[
  {
    "path": ".github/.gitignore",
    "content": "# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\n.goassets\n"
  },
  {
    "path": ".github/fetch-scripts.sh",
    "content": "#!/bin/sh\n\n#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nset -eu\n\nSCRIPT_PATH=\"$(realpath \"$(dirname \"$0\")\")\"\nGOASSETS_PATH=\"${SCRIPT_PATH}/.goassets\"\n\nGOASSETS_REF=${GOASSETS_REF:-main}\n\nif [ -d \"${GOASSETS_PATH}\" ]; then\n  if ! git -C \"${GOASSETS_PATH}\" diff --exit-code; then\n    echo \"${GOASSETS_PATH} has uncommitted changes\" >&2\n    exit 1\n  fi\n  git -C \"${GOASSETS_PATH}\" fetch origin\n  git -C \"${GOASSETS_PATH}\" checkout ${GOASSETS_REF}\n  git -C \"${GOASSETS_PATH}\" reset --hard origin/${GOASSETS_REF}\nelse\n  git clone -b ${GOASSETS_REF} https://github.com/pion/.goassets.git \"${GOASSETS_PATH}\"\nfi\n"
  },
  {
    "path": ".github/install-hooks.sh",
    "content": "#!/bin/sh\n\n#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nSCRIPT_PATH=\"$(realpath \"$(dirname \"$0\")\")\"\n\n. ${SCRIPT_PATH}/fetch-scripts.sh\n\ncp \"${GOASSETS_PATH}/hooks/commit-msg.sh\" \"${SCRIPT_PATH}/../.git/hooks/commit-msg\"\ncp \"${GOASSETS_PATH}/hooks/pre-commit.sh\" \"${SCRIPT_PATH}/../.git/hooks/pre-commit\"\n"
  },
  {
    "path": ".github/workflows/api.yaml",
    "content": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this repository should have package specific CI config,\n# remove the repository name from .goassets/.github/workflows/assets-sync.yml.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nname: API\non:\n  pull_request:\n\njobs:\n  check:\n    uses: pion/.goassets/.github/workflows/api.reusable.yml@main\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this repository should have package specific CI config,\n# remove the repository name from .goassets/.github/workflows/assets-sync.yml.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nname: CodeQL\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '23 5 * * 0'\n  pull_request:\n    branches:\n      - main\n    paths:\n      - '**.go'\n\njobs:\n  analyze:\n    uses: pion/.goassets/.github/workflows/codeql-analysis.reusable.yml@main\n"
  },
  {
    "path": ".github/workflows/fuzz.yaml",
    "content": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this repository should have package specific CI config,\n# remove the repository name from .goassets/.github/workflows/assets-sync.yml.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nname: Fuzz\non:\n  push:\n    branches:\n      - main\n  schedule:\n    - cron: \"0 */8 * * *\"\n\njobs:\n  fuzz:\n    uses: pion/.goassets/.github/workflows/fuzz.reusable.yml@main\n    with:\n      go-version: \"1.25\" # auto-update/latest-go-version\n      fuzz-time: \"60s\"\n"
  },
  {
    "path": ".github/workflows/lint.yaml",
    "content": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this repository should have package specific CI config,\n# remove the repository name from .goassets/.github/workflows/assets-sync.yml.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nname: Lint\non:\n  pull_request:\n\njobs:\n  lint:\n    uses: pion/.goassets/.github/workflows/lint.reusable.yml@main\n    with:\n      golangci-lint-version: v2.10.1\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this repository should have package specific CI config,\n# remove the repository name from .goassets/.github/workflows/assets-sync.yml.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nname: Release\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  release:\n    uses: pion/.goassets/.github/workflows/release.reusable.yml@main\n    with:\n      go-version: \"1.25\" # auto-update/latest-go-version\n"
  },
  {
    "path": ".github/workflows/renovate-go-sum-fix.yaml",
    "content": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this repository should have package specific CI config,\n# remove the repository name from .goassets/.github/workflows/assets-sync.yml.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nname: Fix go.sum\non:\n  push:\n    branches:\n      - renovate/*\n\njobs:\n  fix:\n    uses: pion/.goassets/.github/workflows/renovate-go-sum-fix.reusable.yml@main\n    secrets:\n      token: ${{ secrets.PIONBOT_PRIVATE_KEY }}\n"
  },
  {
    "path": ".github/workflows/reuse.yml",
    "content": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this repository should have package specific CI config,\n# remove the repository name from .goassets/.github/workflows/assets-sync.yml.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nname: REUSE Compliance Check\n\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    uses: pion/.goassets/.github/workflows/reuse.reusable.yml@main\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this repository should have package specific CI config,\n# remove the repository name from .goassets/.github/workflows/assets-sync.yml.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nname: Test\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  test:\n    uses: pion/.goassets/.github/workflows/test.reusable.yml@main\n    strategy:\n      matrix:\n        go: [\"1.25\", \"1.24\"] # auto-update/supported-go-version-list\n      fail-fast: false\n    with:\n      go-version: ${{ matrix.go }}\n    secrets: inherit\n\n  test-i386:\n    uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@main\n    strategy:\n      matrix:\n        go: [\"1.25\", \"1.24\"] # auto-update/supported-go-version-list\n      fail-fast: false\n    with:\n      go-version: ${{ matrix.go }}\n\n  test-windows:\n    uses: pion/.goassets/.github/workflows/test-windows.reusable.yml@main\n    strategy:\n      matrix:\n        go: [\"1.25\", \"1.24\"] # auto-update/supported-go-version-list\n      fail-fast: false\n    with:\n      go-version: ${{ matrix.go }}\n\n  test-macos:\n    uses: pion/.goassets/.github/workflows/test-macos.reusable.yml@main\n    strategy:\n      matrix:\n        go: [\"1.25\", \"1.24\"] # auto-update/supported-go-version-list\n      fail-fast: false\n    with:\n      go-version: ${{ matrix.go }}\n\n  test-wasm:\n    uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@main\n    with:\n      go-version: \"1.25\" # auto-update/latest-go-version\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/tidy-check.yaml",
    "content": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this repository should have package specific CI config,\n# remove the repository name from .goassets/.github/workflows/assets-sync.yml.\n#\n# If you want to update the shared CI config, send a PR to\n# https://github.com/pion/.goassets instead of this repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nname: Go mod tidy\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\njobs:\n  tidy:\n    uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@main\n    with:\n      go-version: \"1.25\" # auto-update/latest-go-version\n"
  },
  {
    "path": ".gitignore",
    "content": "# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\n### JetBrains IDE ###\n#####################\n.idea/\n\n### Emacs Temporary Files ###\n#############################\n*~\n\n### Folders ###\n###############\nbin/\nvendor/\nnode_modules/\n\n### Files ###\n#############\n*.ivf\n*.ogg\ntags\ncover.out\n*.sw[poe]\n*.wasm\nexamples/sfu-ws/cert.pem\nexamples/sfu-ws/key.pem\nwasm_exec.js\n"
  },
  {
    "path": ".golangci.yml",
    "content": "# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nversion: \"2\"\nlinters:\n  enable:\n    - asciicheck       # Simple linter to check that your code does not contain non-ASCII identifiers\n    - bidichk          # Checks for dangerous unicode character sequences\n    - bodyclose        # checks whether HTTP response body is closed successfully\n    - containedctx     # containedctx is a linter that detects struct contained context.Context field\n    - contextcheck     # check the function whether use a non-inherited context\n    - cyclop           # checks function and package cyclomatic complexity\n    - decorder         # check declaration order and count of types, constants, variables and functions\n    - dogsled          # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())\n    - dupl             # Tool for code clone detection\n    - durationcheck    # check for two durations multiplied together\n    - err113           # Golang linter to check the errors handling expressions\n    - errcheck         # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases\n    - errchkjson       # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted.\n    - errname          # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.\n    - errorlint        # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.\n    - exhaustive       # check exhaustiveness of enum switch statements\n    - forbidigo        # Forbids identifiers\n    - forcetypeassert  # finds forced type assertions\n    - gochecknoglobals # Checks that no globals are present in Go code\n    - gocognit         # Computes and checks the cognitive complexity of functions\n    - goconst          # Finds repeated strings that could be replaced by a constant\n    - gocritic         # The most opinionated Go source code linter\n    - gocyclo          # Computes and checks the cyclomatic complexity of functions\n    - godot            # Check if comments end in a period\n    - godox            # Tool for detection of FIXME, TODO and other comment keywords\n    - goheader         # Checks is file header matches to pattern\n    - gomoddirectives  # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.\n    - goprintffuncname # Checks that printf-like functions are named with `f` at the end\n    - gosec            # Inspects source code for security problems\n    - govet            # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string\n    - grouper          # An analyzer to analyze expression groups.\n    - importas         # Enforces consistent import aliases\n    - ineffassign      # Detects when assignments to existing variables are not used\n    - lll              # Reports long lines\n    - maintidx         # maintidx measures the maintainability index of each function.\n    - makezero         # Finds slice declarations with non-zero initial length\n    - misspell         # Finds commonly misspelled English words in comments\n    - modernize        # Replace and suggests simplifications to code\n    - nakedret         # Finds naked returns in functions greater than a specified function length\n    - nestif           # Reports deeply nested if statements\n    - nilerr           # Finds the code that returns nil even if it checks that the error is not nil.\n    - nilnil           # Checks that there is no simultaneous return of `nil` error and an invalid value.\n    - nlreturn         # nlreturn checks for a new line before return and branch statements to increase code clarity\n    - noctx            # noctx finds sending http request without context.Context\n    - predeclared      # find code that shadows one of Go's predeclared identifiers\n    - revive           # golint replacement, finds style mistakes\n    - staticcheck      # Staticcheck is a go vet on steroids, applying a ton of static analysis checks\n    - tagliatelle      # Checks the struct tags.\n    - thelper          # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers\n    - unconvert        # Remove unnecessary type conversions\n    - unparam          # Reports unused function parameters\n    - unused           # Checks Go code for unused constants, variables, functions and types\n    - varnamelen       # checks that the length of a variable's name matches its scope\n    - wastedassign     # wastedassign finds wasted assignment statements\n    - whitespace       # Tool for detection of leading and trailing whitespace\n  disable:\n    - depguard         # Go linter that checks if package imports are in a list of acceptable packages\n    - funlen           # Tool for detection of long functions\n    - gochecknoinits   # Checks that no init functions are present in Go code\n    - gomodguard       # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.\n    - interfacebloat   # A linter that checks length of interface.\n    - ireturn          # Accept Interfaces, Return Concrete Types\n    - mnd              # An analyzer to detect magic numbers\n    - nolintlint       # Reports ill-formed or insufficient nolint directives\n    - paralleltest     # paralleltest detects missing usage of t.Parallel() method in your Go test\n    - prealloc         # Finds slice declarations that could potentially be preallocated\n    - promlinter       # Check Prometheus metrics naming via promlint\n    - rowserrcheck     # checks whether Err of rows is checked successfully\n    - sqlclosecheck    # Checks that sql.Rows and sql.Stmt are closed.\n    - testpackage      # linter that makes you use a separate _test package\n    - tparallel        # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes\n    - wrapcheck        # Checks that errors returned from external packages are wrapped\n    - wsl              # Whitespace Linter - Forces you to use empty lines!\n  settings:\n    staticcheck:\n      checks:\n        - all\n        - -QF1008 # \"could remove embedded field\", to keep it explicit!\n        - -QF1003 # \"could use tagged switch on enum\", Cases conflicts with exhaustive!\n    exhaustive:\n      default-signifies-exhaustive: true\n    forbidigo:\n      forbid:\n        - pattern: ^fmt.Print(f|ln)?$\n        - pattern: ^log.(Panic|Fatal|Print)(f|ln)?$\n        - pattern: ^os.Exit$\n        - pattern: ^panic$\n        - pattern: ^print(ln)?$\n        - pattern: ^testing.T.(Error|Errorf|Fatal|Fatalf|Fail|FailNow)$\n          pkg: ^testing$\n          msg: use testify/assert instead\n      analyze-types: true\n    gomodguard:\n      blocked:\n        modules:\n          - github.com/pkg/errors:\n              recommendations:\n                - errors\n    govet:\n      enable:\n        - shadow\n    revive:\n      rules:\n        # Prefer 'any' type alias over 'interface{}' for Go 1.18+ compatibility\n        - name: use-any\n          severity: warning\n          disabled: false\n    misspell:\n      locale: US\n    varnamelen:\n      max-distance: 12\n      min-name-length: 2\n      ignore-type-assert-ok: true\n      ignore-map-index-ok: true\n      ignore-chan-recv-ok: true\n      ignore-decls:\n        - i int\n        - n int\n        - w io.Writer\n        - r io.Reader\n        - b []byte\n  exclusions:\n    generated: lax\n    rules:\n      - linters:\n          - forbidigo\n          - gocognit\n        path: (examples|main\\.go)\n      - linters:\n          - gocognit\n        path: _test\\.go\n      - linters:\n          - forbidigo\n        path: cmd\nformatters:\n  enable:\n    - gci              # Gci control golang package import order and make it always deterministic.\n    - gofmt            # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification\n    - gofumpt          # Gofumpt checks whether code was gofumpt-ed.\n    - goimports        # Goimports does everything that gofmt does. Additionally it checks unused imports\n  exclusions:\n    generated: lax\nissues:\n  max-issues-per-linter: 0\n  max-same-issues: 0"
  },
  {
    "path": ".goreleaser.yml",
    "content": "# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nbuilds:\n- skip: true\n"
  },
  {
    "path": ".reuse/dep5",
    "content": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: Pion\nSource: https://github.com/pion/\n\nFiles: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum **/go.mod **/go.sum .eslintrc.json package.json examples.json sfu-ws/flutter/.gitignore sfu-ws/flutter/pubspec.yaml c-data-channels/webrtc.h examples/examples.json yarn.lock\nCopyright: 2026 The Pion community <https://pion.ly>\nLicense: MIT\n\nFiles: testdata/seed/* testdata/fuzz/* **/testdata/fuzz/* api/*.txt\nCopyright: 2026 The Pion community <https://pion.ly>\nLicense: CC0-1.0\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 The Pion community <https://pion.ly>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "LICENSES/CC0-1.0.txt",
    "content": "Creative Commons Legal Code\n\nCC0 1.0 Universal\n\n    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE\n    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN\n    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS\n    INFORMATION ON AN \"AS-IS\" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES\n    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS\n    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM\n    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED\n    HEREUNDER.\n\nStatement of Purpose\n\nThe laws of most jurisdictions throughout the world automatically confer\nexclusive Copyright and Related Rights (defined below) upon the creator\nand subsequent owner(s) (each and all, an \"owner\") of an original work of\nauthorship and/or a database (each, a \"Work\").\n\nCertain owners wish to permanently relinquish those rights to a Work for\nthe purpose of contributing to a commons of creative, cultural and\nscientific works (\"Commons\") that the public can reliably and without fear\nof later claims of infringement build upon, modify, incorporate in other\nworks, reuse and redistribute as freely as possible in any form whatsoever\nand for any purposes, including without limitation commercial purposes.\nThese owners may contribute to the Commons to promote the ideal of a free\nculture and the further production of creative, cultural and scientific\nworks, or to gain reputation or greater distribution for their Work in\npart through the use and efforts of others.\n\nFor these and/or other purposes and motivations, and without any\nexpectation of additional consideration or compensation, the person\nassociating CC0 with a Work (the \"Affirmer\"), to the extent that he or she\nis an owner of Copyright and Related Rights in the Work, voluntarily\nelects to apply CC0 to the Work and publicly distribute the Work under its\nterms, with knowledge of his or her Copyright and Related Rights in the\nWork and the meaning and intended legal effect of CC0 on those rights.\n\n1. Copyright and Related Rights. A Work made available under CC0 may be\nprotected by copyright and related or neighboring rights (\"Copyright and\nRelated Rights\"). Copyright and Related Rights include, but are not\nlimited to, the following:\n\n  i. the right to reproduce, adapt, distribute, perform, display,\n     communicate, and translate a Work;\n ii. moral rights retained by the original author(s) and/or performer(s);\niii. publicity and privacy rights pertaining to a person's image or\n     likeness depicted in a Work;\n iv. rights protecting against unfair competition in regards to a Work,\n     subject to the limitations in paragraph 4(a), below;\n  v. rights protecting the extraction, dissemination, use and reuse of data\n     in a Work;\n vi. database rights (such as those arising under Directive 96/9/EC of the\n     European Parliament and of the Council of 11 March 1996 on the legal\n     protection of databases, and under any national implementation\n     thereof, including any amended or successor version of such\n     directive); and\nvii. other similar, equivalent or corresponding rights throughout the\n     world based on applicable law or treaty, and any national\n     implementations thereof.\n\n2. Waiver. To the greatest extent permitted by, but not in contravention\nof, applicable law, Affirmer hereby overtly, fully, permanently,\nirrevocably and unconditionally waives, abandons, and surrenders all of\nAffirmer's Copyright and Related Rights and associated claims and causes\nof action, whether now known or unknown (including existing as well as\nfuture claims and causes of action), in the Work (i) in all territories\nworldwide, (ii) for the maximum duration provided by applicable law or\ntreaty (including future time extensions), (iii) in any current or future\nmedium and for any number of copies, and (iv) for any purpose whatsoever,\nincluding without limitation commercial, advertising or promotional\npurposes (the \"Waiver\"). Affirmer makes the Waiver for the benefit of each\nmember of the public at large and to the detriment of Affirmer's heirs and\nsuccessors, fully intending that such Waiver shall not be subject to\nrevocation, rescission, cancellation, termination, or any other legal or\nequitable action to disrupt the quiet enjoyment of the Work by the public\nas contemplated by Affirmer's express Statement of Purpose.\n\n3. Public License Fallback. Should any part of the Waiver for any reason\nbe judged legally invalid or ineffective under applicable law, then the\nWaiver shall be preserved to the maximum extent permitted taking into\naccount Affirmer's express Statement of Purpose. In addition, to the\nextent the Waiver is so judged Affirmer hereby grants to each affected\nperson a royalty-free, non transferable, non sublicensable, non exclusive,\nirrevocable and unconditional license to exercise Affirmer's Copyright and\nRelated Rights in the Work (i) in all territories worldwide, (ii) for the\nmaximum duration provided by applicable law or treaty (including future\ntime extensions), (iii) in any current or future medium and for any number\nof copies, and (iv) for any purpose whatsoever, including without\nlimitation commercial, advertising or promotional purposes (the\n\"License\"). The License shall be deemed effective as of the date CC0 was\napplied by Affirmer to the Work. Should any part of the License for any\nreason be judged legally invalid or ineffective under applicable law, such\npartial invalidity or ineffectiveness shall not invalidate the remainder\nof the License, and in such case Affirmer hereby affirms that he or she\nwill not (i) exercise any of his or her remaining Copyright and Related\nRights in the Work or (ii) assert any associated claims and causes of\naction with respect to the Work, in either case contrary to Affirmer's\nexpress Statement of Purpose.\n\n4. Limitations and Disclaimers.\n\n a. No trademark or patent rights held by Affirmer are waived, abandoned,\n    surrendered, licensed or otherwise affected by this document.\n b. Affirmer offers the Work as-is and makes no representations or\n    warranties of any kind concerning the Work, express, implied,\n    statutory or otherwise, including without limitation warranties of\n    title, merchantability, fitness for a particular purpose, non\n    infringement, or the absence of latent or other defects, accuracy, or\n    the present or absence of errors, whether or not discoverable, all to\n    the greatest extent permissible under applicable law.\n c. Affirmer disclaims responsibility for clearing rights of other persons\n    that may apply to the Work or any use thereof, including without\n    limitation any person's Copyright and Related Rights in the Work.\n    Further, Affirmer disclaims responsibility for obtaining any necessary\n    consents, permissions or other rights required for any use of the\n    Work.\n d. Affirmer understands and acknowledges that Creative Commons is not a\n    party to this document and has no duty or obligation with respect to\n    this CC0 or use of the Work.\n"
  },
  {
    "path": "LICENSES/MIT.txt",
    "content": "MIT License\n\nCopyright (c) <year> <copyright holders>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">\n  <br>\n  Pion RTCP\n  <br>\n</h1>\n<h4 align=\"center\">A Go implementation of RTCP</h4>\n<p align=\"center\">\n  <a href=\"https://pion.ly\"><img src=\"https://img.shields.io/badge/pion-rtcp-gray.svg?longCache=true&colorB=brightgreen\" alt=\"Pion RTCP\"></a>\n  <a href=\"https://sourcegraph.com/github.com/pion/rtcp?badge\"><img src=\"https://sourcegraph.com/github.com/pion/rtcp/-/badge.svg\" alt=\"Sourcegraph Widget\"></a>\n  <a href=\"https://discord.gg/PngbdqpFbt\"><img src=\"https://img.shields.io/badge/join-us%20on%20discord-gray.svg?longCache=true&logo=discord&colorB=brightblue\" alt=\"join us on Discord\"></a> <a href=\"https://bsky.app/profile/pion.ly\"><img src=\"https://img.shields.io/badge/follow-us%20on%20bluesky-gray.svg?longCache=true&logo=bluesky&colorB=brightblue\" alt=\"Follow us on Bluesky\"></a>\n  <br>\n  <img alt=\"GitHub Workflow Status\" src=\"https://img.shields.io/github/actions/workflow/status/pion/rtcp/test.yaml\">\n  <a href=\"https://pkg.go.dev/github.com/pion/rtcp\"><img src=\"https://pkg.go.dev/badge/github.com/pion/rtcp.svg\" alt=\"Go Reference\"></a>\n  <a href=\"https://codecov.io/gh/pion/rtcp\"><img src=\"https://codecov.io/gh/pion/rtcp/branch/master/graph/badge.svg\" alt=\"Coverage Status\"></a>\n  <a href=\"https://goreportcard.com/report/github.com/pion/rtcp\"><img src=\"https://goreportcard.com/badge/github.com/pion/rtcp\" alt=\"Go Report Card\"></a>\n  <a href=\"LICENSE\"><img src=\"https://img.shields.io/badge/License-MIT-yellow.svg\" alt=\"License: MIT\"></a>\n</p>\n<br>\n\nSee [DESIGN.md](DESIGN.md) for an overview of features and future goals.\n\n### Roadmap\nThe library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.\n\n### Community\nPion has an active community on the [Discord](https://discord.gg/PngbdqpFbt).\n\nFollow the [Pion Bluesky](https://bsky.app/profile/pion.ly) or [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.\n\nWe are always looking to support **your projects**. Please reach out if you have something to build!\nIf you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)\n\n### Contributing\nCheck out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible\n\n### License\nMIT License - see [LICENSE](LICENSE) for full text\n"
  },
  {
    "path": "application_defined.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n)\n\n// ApplicationDefined represents an RTCP application-defined packet.\ntype ApplicationDefined struct {\n\tSubType uint8\n\tSSRC    uint32\n\tName    string\n\tData    []byte\n}\n\n// DestinationSSRC returns the SSRC value for this packet.\nfunc (a ApplicationDefined) DestinationSSRC() []uint32 {\n\treturn []uint32{a.SSRC}\n}\n\n// Marshal serializes the application-defined struct into a byte slice with padding.\nfunc (a ApplicationDefined) Marshal() ([]byte, error) {\n\tdataLength := len(a.Data)\n\tif dataLength > 0xFFFF-12 {\n\t\treturn nil, errAppDefinedDataTooLarge\n\t}\n\tif len(a.Name) != 4 {\n\t\treturn nil, errAppDefinedInvalidName\n\t}\n\t// Calculate the padding size to be added to make the packet length a multiple of 4 bytes.\n\tpaddingSize := 4 - (dataLength % 4)\n\tif paddingSize == 4 {\n\t\tpaddingSize = 0\n\t}\n\n\tpacketSize := a.MarshalSize()\n\theader := Header{\n\t\tType:    TypeApplicationDefined,\n\t\tLength:  uint16((packetSize / 4) - 1), //nolint:gosec // G115\n\t\tPadding: paddingSize != 0,\n\t\tCount:   a.SubType,\n\t}\n\n\theaderBytes, err := header.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trawPacket := make([]byte, packetSize)\n\tcopy(rawPacket, headerBytes)\n\tbinary.BigEndian.PutUint32(rawPacket[4:8], a.SSRC)\n\tcopy(rawPacket[8:12], a.Name)\n\tcopy(rawPacket[12:], a.Data)\n\n\t// Add padding if necessary.\n\tif paddingSize > 0 {\n\t\tfor i := 0; i < paddingSize; i++ {\n\t\t\trawPacket[12+dataLength+i] = byte(paddingSize)\n\t\t}\n\t}\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal parses the given raw packet into an application-defined struct, handling padding.\nfunc (a *ApplicationDefined) Unmarshal(rawPacket []byte) error {\n\t/*\n\t    0                   1                   2                   3\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   |V=2|P| subtype |   PT=APP=204  |             length            |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |                           SSRC/CSRC                           |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |                          name (ASCII)                         |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |                   application-dependent data                ...\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t*/\n\theader := Header{}\n\terr := header.Unmarshal(rawPacket)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(rawPacket) < 12 {\n\t\treturn errPacketTooShort\n\t}\n\n\tif int(header.Length+1)*4 != len(rawPacket) {\n\t\treturn errAppDefinedInvalidLength\n\t}\n\n\ta.SubType = header.Count\n\ta.SSRC = binary.BigEndian.Uint32(rawPacket[4:8])\n\ta.Name = string(rawPacket[8:12])\n\n\t// Check for padding.\n\tpaddingSize := 0\n\tif header.Padding {\n\t\tpaddingSize = int(rawPacket[len(rawPacket)-1])\n\t\tif paddingSize > len(rawPacket)-12 {\n\t\t\treturn errWrongPadding\n\t\t}\n\t}\n\n\ta.Data = rawPacket[12 : len(rawPacket)-paddingSize]\n\n\treturn nil\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (a *ApplicationDefined) MarshalSize() int {\n\tdataLength := len(a.Data)\n\t// Calculate the padding size to be added to make the packet length a multiple of 4 bytes.\n\tpaddingSize := 4 - (dataLength % 4)\n\tif paddingSize == 4 {\n\t\tpaddingSize = 0\n\t}\n\n\treturn 12 + dataLength + paddingSize\n}\n"
  },
  {
    "path": "application_defined_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestTApplicationPacketUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      ApplicationDefined\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tData: []byte{\n\t\t\t\t// Application Packet Type + Length(0x0003)\n\t\t\t\t0x80, 0xcc, 0x00, 0x03,\n\t\t\t\t// sender=0x4baae1ab\n\t\t\t\t0x4b, 0xaa, 0xe1, 0xab,\n\t\t\t\t// name='NAME'\n\t\t\t\t0x4E, 0x41, 0x4D, 0x45,\n\t\t\t\t// data='ABCD'\n\t\t\t\t0x41, 0x42, 0x43, 0x44,\n\t\t\t},\n\t\t\tWant: ApplicationDefined{\n\t\t\t\tSubType: 0,\n\t\t\t\tSSRC:    0x4baae1ab,\n\t\t\t\tName:    \"NAME\",\n\t\t\t\tData:    []byte{0x41, 0x42, 0x43, 0x44},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validCustomSsubType\",\n\t\t\tData: []byte{\n\t\t\t\t// Application Packet Type (SubType 31) + Length(0x0003)\n\t\t\t\t0x9f, 0xcc, 0x00, 0x03,\n\t\t\t\t// sender=0x4baae1ab\n\t\t\t\t0x4b, 0xaa, 0xe1, 0xab,\n\t\t\t\t// name='NAME'\n\t\t\t\t0x4E, 0x41, 0x4D, 0x45,\n\t\t\t\t// data='ABCD'\n\t\t\t\t0x41, 0x42, 0x43, 0x44,\n\t\t\t},\n\t\t\tWant: ApplicationDefined{\n\t\t\t\tSubType: 31,\n\t\t\t\tSSRC:    0x4baae1ab,\n\t\t\t\tName:    \"NAME\",\n\t\t\t\tData:    []byte{0x41, 0x42, 0x43, 0x44},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validWithPadding\",\n\t\t\tData: []byte{\n\t\t\t\t// Application Packet Type + Length(0x0002)  (0xA0 has padding bit set)\n\t\t\t\t0xA0, 0xcc, 0x00, 0x04,\n\t\t\t\t// sender=0x4baae1ab\n\t\t\t\t0x4b, 0xaa, 0xe1, 0xab,\n\t\t\t\t// name='NAME'\n\t\t\t\t0x4E, 0x41, 0x4D, 0x45,\n\t\t\t\t// data='ABCDE'\n\t\t\t\t0x41, 0x42, 0x43, 0x44, 0x45,\n\t\t\t\t// 3 bytes padding as packet length must be a division of 4\n\t\t\t\t0x03, 0x03, 0x03,\n\t\t\t},\n\t\t\tWant: ApplicationDefined{\n\t\t\t\tSubType: 0,\n\t\t\t\tSSRC:    0x4baae1ab,\n\t\t\t\tName:    \"NAME\",\n\t\t\t\tData:    []byte{0x41, 0x42, 0x43, 0x44, 0x45},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"invalidAppPacketLengthField\",\n\t\t\tData: []byte{\n\t\t\t\t// Application Packet Type + invalid Length(0x00FF)\n\t\t\t\t0x80, 0xcc, 0x00, 0xFF,\n\t\t\t\t// sender=0x4baae1ab\n\t\t\t\t0x4b, 0xaa, 0xe1, 0xab,\n\t\t\t\t// name='NAME'\n\t\t\t\t0x4E, 0x41, 0x4D, 0x45,\n\t\t\t\t// data='ABCD'\n\t\t\t\t0x41, 0x42, 0x43, 0x44,\n\t\t\t},\n\t\t\tWantError: errAppDefinedInvalidLength,\n\t\t},\n\t\t{\n\t\t\tName: \"invalidPacketLengthTooShort\",\n\t\t\tData: []byte{\n\t\t\t\t// Application Packet Type + Length(0x0002). Total packet length is less than 12 bytes\n\t\t\t\t0x80, 0xcc, 0x00, 0x2,\n\t\t\t\t// sender=0x4baae1ab\n\t\t\t\t0x4b, 0xaa, 0xe1, 0xab,\n\t\t\t\t// name='SUI'\n\t\t\t\t0x53, 0x55, 0x49,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"wrongPaddingSize\",\n\t\t\tData: []byte{\n\t\t\t\t// Application Packet Type + Length(0x0002)  (0xA0 has padding bit set)\n\t\t\t\t0xA0, 0xcc, 0x00, 0x04,\n\t\t\t\t// sender=0x4baae1ab\n\t\t\t\t0x4b, 0xaa, 0xe1, 0xab,\n\t\t\t\t// name='NAME'\n\t\t\t\t0x4E, 0x41, 0x4D, 0x45,\n\t\t\t\t// data='ABCDE'\n\t\t\t\t0x41, 0x42, 0x43, 0x44, 0x45,\n\t\t\t\t// 3 bytes padding as packet length must be a division of 4\n\t\t\t\t0x03, 0x03, 0x09, // last byte has padding size 0x09 which is more than the data + padding bytes\n\t\t\t},\n\t\t\tWantError: errWrongPadding,\n\t\t},\n\t\t{\n\t\t\tName: \"invalidHeader\",\n\t\t\tData: []byte{\n\t\t\t\t// Application Packet Type + invalid Length(0x00FF)\n\t\t\t\t0xFF,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t} {\n\t\tvar apk ApplicationDefined\n\t\terr := apk.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equalf(t, test.Want, apk, \"Unmarshal %q\", test.Name)\n\n\t\t// Check SSRC is matching\n\t\tassert.Equalf(t, uint32(0x4baae1ab), apk.SSRC, \"%q SSRC mismatch\", test.Name)\n\t\tassert.Equalf(t, uint32(0x4baae1ab), apk.DestinationSSRC()[0], \"%q DestinationSSRC mismatch\", test.Name)\n\t}\n}\n\nfunc TestTApplicationPacketMarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tWant      []byte\n\t\tPacket    ApplicationDefined\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tWant: []byte{\n\t\t\t\t// Application Packet Type + Length(0x0003)\n\t\t\t\t0x80, 0xcc, 0x00, 0x03,\n\t\t\t\t// sender=0x4baae1ab\n\t\t\t\t0x4b, 0xaa, 0xe1, 0xab,\n\t\t\t\t// name='NAME'\n\t\t\t\t0x4E, 0x41, 0x4D, 0x45,\n\t\t\t\t// data='ABCD'\n\t\t\t\t0x41, 0x42, 0x43, 0x44,\n\t\t\t},\n\t\t\tPacket: ApplicationDefined{\n\t\t\t\tSSRC: 0x4baae1ab,\n\t\t\t\tName: \"NAME\",\n\t\t\t\tData: []byte{0x41, 0x42, 0x43, 0x44},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validCustomSubType\",\n\t\t\tWant: []byte{\n\t\t\t\t// Application Packet Type (SubType 31) + Length(0x0003)\n\t\t\t\t0x9f, 0xcc, 0x00, 0x03,\n\t\t\t\t// sender=0x4baae1ab\n\t\t\t\t0x4b, 0xaa, 0xe1, 0xab,\n\t\t\t\t// name='NAME'\n\t\t\t\t0x4E, 0x41, 0x4D, 0x45,\n\t\t\t\t// data='ABCD'\n\t\t\t\t0x41, 0x42, 0x43, 0x44,\n\t\t\t},\n\t\t\tPacket: ApplicationDefined{\n\t\t\t\tSubType: 31,\n\t\t\t\tSSRC:    0x4baae1ab,\n\t\t\t\tName:    \"NAME\",\n\t\t\t\tData:    []byte{0x41, 0x42, 0x43, 0x44},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validWithPadding\",\n\t\t\tWant: []byte{\n\t\t\t\t// Application Packet Type + Length(0x0002)  (0xA0 has padding bit set)\n\t\t\t\t0xA0, 0xcc, 0x00, 0x04,\n\t\t\t\t// sender=0x4baae1ab\n\t\t\t\t0x4b, 0xaa, 0xe1, 0xab,\n\t\t\t\t// name='NAME'\n\t\t\t\t0x4E, 0x41, 0x4D, 0x45,\n\t\t\t\t// data='ABCDE'\n\t\t\t\t0x41, 0x42, 0x43, 0x44, 0x45,\n\t\t\t\t// 3 bytes padding as packet length must be a division of 4\n\t\t\t\t0x03, 0x03, 0x03,\n\t\t\t},\n\t\t\tPacket: ApplicationDefined{\n\t\t\t\tSSRC: 0x4baae1ab,\n\t\t\t\tName: \"NAME\",\n\t\t\t\tData: []byte{0x41, 0x42, 0x43, 0x44, 0x45},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:      \"invalidDataTooLarge\",\n\t\t\tWantError: errAppDefinedDataTooLarge,\n\t\t\tPacket: ApplicationDefined{\n\t\t\t\tSSRC: 0x4baae1ab,\n\t\t\t\tName: \"NAME\",\n\t\t\t\tData: make([]byte, 0xFFFF-12+1), // total max packet size is 0xFFFF including header and other fields.\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:      \"invalidName\",\n\t\t\tWantError: errAppDefinedInvalidName,\n\t\t\tPacket: ApplicationDefined{\n\t\t\t\tSSRC: 0x4baae1ab,\n\t\t\t\tName: \"NOT4CHARS\",\n\t\t\t\tData: []byte{0x41, 0x42, 0x43, 0x44},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:      \"InvalidSubType\",\n\t\t\tWantError: errInvalidHeader,\n\t\t\tPacket: ApplicationDefined{\n\t\t\t\tSubType: 32, // Must be up to 31\n\t\t\t\tSSRC:    0x4baae1ab,\n\t\t\t\tName:    \"NAME\",\n\t\t\t\tData:    []byte{0x41, 0x42, 0x43, 0x44},\n\t\t\t},\n\t\t},\n\t} {\n\t\trawPacket, err := test.Packet.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equalf(t, test.Want, rawPacket, \"Marshal %q\", test.Name)\n\n\t\tmarshalSize := test.Packet.MarshalSize()\n\t\tassert.Equalf(t, marshalSize, len(rawPacket), \"MarshalSize %q\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "codecov.yml",
    "content": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n#\n# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\ncoverage:\n  status:\n    project:\n      default:\n        # Allow decreasing 2% of total coverage to avoid noise.\n        threshold: 2%\n    patch:\n      default:\n        target: 70%\n        only_pulls: true\n\nignore:\n  - \"examples/*\"\n  - \"examples/**/*\"\n"
  },
  {
    "path": "compound_packet.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// A CompoundPacket is a collection of RTCP packets transmitted as a single packet with\n// the underlying protocol (for example UDP).\n//\n// To maximize the resolution of receiption statistics, the first Packet in a CompoundPacket\n// must always be either a SenderReport or a ReceiverReport.  This is true even if no data\n// has been sent or received, in which case an empty ReceiverReport must be sent, and even\n// if the only other RTCP packet in the compound packet is a Goodbye.\n//\n// Next, a SourceDescription containing a CNAME item must be included in each CompoundPacket\n// to identify the source and to begin associating media for purposes such as lip-sync.\n//\n// Other RTCP packet types may follow in any order. Packet types may appear more than once.\ntype CompoundPacket []Packet\n\n// Validate returns an error if this is not an RFC-compliant CompoundPacket.\n//\n//nolint:cyclop\nfunc (c CompoundPacket) Validate() error {\n\tif len(c) == 0 {\n\t\treturn errEmptyCompound\n\t}\n\n\t// SenderReport and ReceiverReport are the only types that\n\t// are allowed to be the first packet in a compound datagram\n\tswitch c[0].(type) {\n\tcase *SenderReport, *ReceiverReport:\n\t\t// ok\n\tdefault:\n\t\treturn errBadFirstPacket\n\t}\n\n\tfor _, pkt := range c[1:] {\n\t\tswitch p := pkt.(type) {\n\t\t// If the number of RecetpionReports exceeds 31 additional ReceiverReports\n\t\t// can be included here.\n\t\tcase *ReceiverReport:\n\t\t\tcontinue\n\n\t\t// A SourceDescription containing a CNAME must be included in every\n\t\t// CompoundPacket.\n\t\tcase *SourceDescription:\n\t\t\tvar hasCNAME bool\n\t\t\tfor _, c := range p.Chunks {\n\t\t\t\tfor _, it := range c.Items {\n\t\t\t\t\tif it.Type == SDESCNAME {\n\t\t\t\t\t\thasCNAME = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !hasCNAME {\n\t\t\t\treturn errMissingCNAME\n\t\t\t}\n\n\t\t\treturn nil\n\n\t\t// Other packets are not permitted before the CNAME\n\t\tdefault:\n\t\t\treturn errPacketBeforeCNAME\n\t\t}\n\t}\n\n\t// CNAME never reached\n\treturn errMissingCNAME\n}\n\n// CNAME returns the CNAME that *must* be present in every CompoundPacket.\nfunc (c CompoundPacket) CNAME() (string, error) {\n\tvar err error\n\n\tif len(c) < 1 {\n\t\treturn \"\", errEmptyCompound\n\t}\n\n\tfor _, pkt := range c[1:] {\n\t\tsdes, ok := pkt.(*SourceDescription)\n\t\tif ok {\n\t\t\tfor _, c := range sdes.Chunks {\n\t\t\t\tfor _, it := range c.Items {\n\t\t\t\t\tif it.Type == SDESCNAME {\n\t\t\t\t\t\treturn it.Text, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t_, ok := pkt.(*ReceiverReport)\n\t\t\tif !ok {\n\t\t\t\terr = errPacketBeforeCNAME\n\t\t\t}\n\t\t}\n\t}\n\n\treturn \"\", errMissingCNAME\n}\n\n// Marshal encodes the CompoundPacket as binary.\nfunc (c CompoundPacket) Marshal() ([]byte, error) {\n\tif err := c.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := []Packet(c)\n\n\treturn Marshal(p)\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (c CompoundPacket) MarshalSize() int {\n\tl := 0\n\tfor _, p := range c {\n\t\tl += p.MarshalSize()\n\t}\n\n\treturn l\n}\n\n// Unmarshal decodes a CompoundPacket from binary.\nfunc (c *CompoundPacket) Unmarshal(rawData []byte) error {\n\tout := make(CompoundPacket, 0)\n\tfor len(rawData) != 0 {\n\t\tp, processed, err := unmarshal(rawData)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tout = append(out, p)\n\t\trawData = rawData[processed:]\n\t}\n\t*c = out\n\n\treturn c.Validate()\n}\n\n// DestinationSSRC returns the synchronization sources associated with this\n// CompoundPacket's reception report.\nfunc (c CompoundPacket) DestinationSSRC() []uint32 {\n\tif len(c) == 0 {\n\t\treturn nil\n\t}\n\n\treturn c[0].DestinationSSRC()\n}\n\nfunc (c CompoundPacket) String() string {\n\tout := \"CompoundPacket\\n\"\n\tfor _, p := range c {\n\t\tstringer, canString := p.(fmt.Stringer)\n\t\tif canString {\n\t\t\tout += stringer.String()\n\t\t} else {\n\t\t\tout += stringify(p)\n\t\t}\n\t}\n\tout = strings.TrimSuffix(strings.ReplaceAll(out, \"\\n\", \"\\n\\t\"), \"\\t\")\n\n\treturn out\n}\n"
  },
  {
    "path": "compound_packet_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*CompoundPacket)(nil) // assert is a Packet\n\nfunc TestReadEOF(t *testing.T) {\n\tshortHeader := []byte{\n\t\t0x81, 0xc9, // missing type & len\n\t}\n\n\t_, err := Unmarshal(shortHeader)\n\tassert.Error(t, err)\n}\n\nfunc TestBadCompound(t *testing.T) {\n\t// trailing data!\n\tbadcompound := realPacket()[:34]\n\tpackets, err := Unmarshal(badcompound)\n\tassert.Error(t, err)\n\n\tassert.Nil(t, packets)\n\n\tbadcompound = realPacket()[84:104]\n\n\tpackets, err = Unmarshal(badcompound)\n\tassert.NoError(t, err)\n\n\tcompound := CompoundPacket(packets)\n\n\t// this should return an error,\n\t// it violates the \"must start with RR or SR\" rule\n\tassert.ErrorIs(t, compound.Validate(), errBadFirstPacket)\n\tassert.Equal(t, 2, len(compound))\n\n\t_, ok := compound[0].(*Goodbye)\n\tassert.True(t, ok)\n\n\t_, ok = compound[1].(*PictureLossIndication)\n\tassert.True(t, ok)\n}\n\nfunc TestValidPacket(t *testing.T) {\n\tcname := NewCNAMESourceDescription(1234, \"cname\")\n\n\tfor _, test := range []struct {\n\t\tName   string\n\t\tPacket CompoundPacket\n\t\tErr    error\n\t}{\n\t\t{\n\t\t\tName:   \"empty\",\n\t\t\tPacket: CompoundPacket{},\n\t\t\tErr:    errEmptyCompound,\n\t\t},\n\t\t{\n\t\t\tName: \"no cname\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&SenderReport{},\n\t\t\t},\n\t\t\tErr: errMissingCNAME,\n\t\t},\n\t\t{\n\t\t\tName: \"just BYE\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&Goodbye{},\n\t\t\t},\n\t\t\tErr: errBadFirstPacket,\n\t\t},\n\t\t{\n\t\t\tName: \"SDES / no cname\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&SenderReport{},\n\t\t\t\t&SourceDescription{},\n\t\t\t},\n\t\t\tErr: errMissingCNAME,\n\t\t},\n\t\t{\n\t\t\tName: \"just SR\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&SenderReport{},\n\t\t\t\tcname,\n\t\t\t},\n\t\t\tErr: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"multiple SRs\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&SenderReport{},\n\t\t\t\t&SenderReport{},\n\t\t\t\tcname,\n\t\t\t},\n\t\t\tErr: errPacketBeforeCNAME,\n\t\t},\n\t\t{\n\t\t\tName: \"just RR\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&ReceiverReport{},\n\t\t\t\tcname,\n\t\t\t},\n\t\t\tErr: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"multiple RRs\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&ReceiverReport{},\n\t\t\t\t&ReceiverReport{},\n\t\t\t\tcname,\n\t\t\t},\n\t\t\tErr: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"goodbye\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&ReceiverReport{},\n\t\t\t\tcname,\n\t\t\t\t&Goodbye{},\n\t\t\t},\n\t\t\tErr: nil,\n\t\t},\n\t} {\n\t\tassert.ErrorIsf(t, test.Packet.Validate(), test.Err, \"Validate(%s)\", test.Name)\n\t}\n}\n\nfunc TestCNAME(t *testing.T) {\n\tcname := NewCNAMESourceDescription(1234, \"cname\")\n\n\tfor _, test := range []struct {\n\t\tName   string\n\t\tPacket CompoundPacket\n\t\tErr    error\n\t\tText   string\n\t}{\n\t\t{\n\t\t\tName: \"no cname\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&SenderReport{},\n\t\t\t},\n\t\t\tErr: errMissingCNAME,\n\t\t},\n\t\t{\n\t\t\tName: \"SDES / no cname\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&SenderReport{},\n\t\t\t\t&SourceDescription{},\n\t\t\t},\n\t\t\tErr: errMissingCNAME,\n\t\t},\n\t\t{\n\t\t\tName: \"just SR\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&SenderReport{},\n\t\t\t\tcname,\n\t\t\t},\n\t\t\tErr:  nil,\n\t\t\tText: \"cname\",\n\t\t},\n\t\t{\n\t\t\tName: \"multiple SRs\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&SenderReport{},\n\t\t\t\t&SenderReport{},\n\t\t\t\tcname,\n\t\t\t},\n\t\t\tErr:  errPacketBeforeCNAME,\n\t\t\tText: \"cname\",\n\t\t},\n\t\t{\n\t\t\tName: \"just RR\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&ReceiverReport{},\n\t\t\t\tcname,\n\t\t\t},\n\t\t\tErr:  nil,\n\t\t\tText: \"cname\",\n\t\t},\n\t\t{\n\t\t\tName: \"multiple RRs\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&ReceiverReport{},\n\t\t\t\t&ReceiverReport{},\n\t\t\t\tcname,\n\t\t\t},\n\t\t\tErr:  nil,\n\t\t\tText: \"cname\",\n\t\t},\n\t\t{\n\t\t\tName: \"goodbye\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&ReceiverReport{},\n\t\t\t\tcname,\n\t\t\t\t&Goodbye{},\n\t\t\t},\n\t\t\tErr:  nil,\n\t\t\tText: \"cname\",\n\t\t},\n\t} {\n\t\tassert.ErrorIsf(t, test.Packet.Validate(), test.Err, \"Validate(%s)\", test.Name)\n\n\t\tname, err := test.Packet.CNAME()\n\t\tassert.ErrorIsf(t, err, test.Err, \"CNAME(%s)\", test.Name)\n\t\tassert.Equalf(t, test.Text, name, \"CNAME(%s)\", test.Name)\n\t}\n}\n\nfunc TestCompoundPacketRoundTrip(t *testing.T) {\n\tcname := NewCNAMESourceDescription(1234, \"cname\")\n\n\tfor _, test := range []struct {\n\t\tName   string\n\t\tPacket CompoundPacket\n\t\tErr    error\n\t}{\n\t\t{\n\t\t\tName: \"bye\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&ReceiverReport{},\n\t\t\t\tcname,\n\t\t\t\t&Goodbye{\n\t\t\t\t\tSources: []uint32{1234},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"no cname\",\n\t\t\tPacket: CompoundPacket{\n\t\t\t\t&ReceiverReport{},\n\t\t\t},\n\t\t\tErr: errMissingCNAME,\n\t\t},\n\t} {\n\t\tdata, err := test.Packet.Marshal()\n\t\tassert.ErrorIsf(t, err, test.Err, \"Marshal(%v)\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar c CompoundPacket\n\t\tassert.NoErrorf(t, c.Unmarshal(data), \"Unmarshal(%v)\", test.Name)\n\n\t\tdata2, err := c.Marshal()\n\t\tassert.NoErrorf(t, err, \"Marshal(%v)\", test.Name)\n\t\tassert.Equalf(t, data, data2, \"Marshal(%v) mismatch\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "doc.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\n/*\nPackage rtcp implements encoding and decoding of RTCP packets according to RFCs 3550 and 5506.\n\nRTCP is a sister protocol of the Real-time Transport Protocol (RTP). Its basic functionality\nand packet structure is defined in RFC 3550. RTCP provides out-of-band statistics and control\ninformation for an RTP session. It partners with RTP in the delivery and packaging of multimedia data,\nbut does not transport any media data itself.\n\nThe primary function of RTCP is to provide feedback on the quality of service (QoS)\nin media distribution by periodically sending statistics information such as transmitted octet\nand packet counts, packet loss, packet delay variation, and round-trip delay time to participants\nin a streaming multimedia session. An application may use this information to control quality of\nservice parameters, perhaps by limiting flow, or using a different codec.\n\nDecoding RTCP packets:\n\n\tpkts, err := rtcp.Unmarshal(rtcpData)\n\t// ...\n\tfor _, pkt := range pkts {\n\t\tswitch p := pkt.(type) {\n\t\tcase *rtcp.CompoundPacket:\n\t\t\t...\n\t\tcase *rtcp.PictureLossIndication:\n\t\t\t...\n\t\tdefault:\n\t\t\t...\n\t\t}\n\t}\n\nEncoding RTCP packets:\n\n\tpkt := &rtcp.PictureLossIndication{\n\t\tSenderSSRC: senderSSRC,\n\t\tMediaSSRC: mediaSSRC\n\t}\n\tpliData, err := pkt.Marshal()\n\t// ...\n*/\npackage rtcp\n"
  },
  {
    "path": "errors.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport \"errors\"\n\nvar (\n\terrWrongMarshalSize         = errors.New(\"rtcp: wrong marshal size\")\n\terrInvalidTotalLost         = errors.New(\"rtcp: invalid total lost count\")\n\terrInvalidHeader            = errors.New(\"rtcp: invalid header\")\n\terrEmptyCompound            = errors.New(\"rtcp: empty compound packet\")\n\terrBadFirstPacket           = errors.New(\"rtcp: first packet in compound must be SR or RR\")\n\terrMissingCNAME             = errors.New(\"rtcp: compound missing SourceDescription with CNAME\")\n\terrPacketBeforeCNAME        = errors.New(\"rtcp: feedback packet seen before CNAME\")\n\terrTooManySSRCs             = errors.New(\"rtcp: too many SSRCs\")\n\terrTooManyReports           = errors.New(\"rtcp: too many reports\")\n\terrTooManyChunks            = errors.New(\"rtcp: too many chunks\")\n\terrTooManySources           = errors.New(\"rtcp: too many sources\")\n\terrPacketTooShort           = errors.New(\"rtcp: packet too short\")\n\terrWrongType                = errors.New(\"rtcp: wrong packet type\")\n\terrSDESTextTooLong          = errors.New(\"rtcp: sdes must be < 255 octets long\")\n\terrSDESMissingType          = errors.New(\"rtcp: sdes item missing type\")\n\terrReasonTooLong            = errors.New(\"rtcp: reason must be < 255 octets long\")\n\terrBadVersion               = errors.New(\"rtcp: invalid packet version\")\n\terrBadLength                = errors.New(\"rtcp: invalid packet length\")\n\terrWrongPadding             = errors.New(\"rtcp: invalid padding value\")\n\terrWrongFeedbackType        = errors.New(\"rtcp: wrong feedback message type\")\n\terrWrongPayloadType         = errors.New(\"rtcp: wrong payload type\")\n\terrHeaderTooSmall           = errors.New(\"rtcp: header length is too small\")\n\terrSSRCMustBeZero           = errors.New(\"rtcp: media SSRC must be 0\")\n\terrMissingREMBidentifier    = errors.New(\"missing REMB identifier\")\n\terrSSRCNumAndLengthMismatch = errors.New(\"SSRC num and length do not match\")\n\terrInvalidSizeOrStartIndex  = errors.New(\"invalid size or startIndex\")\n\terrInvalidBitrate           = errors.New(\"invalid bitrate\")\n\terrWrongChunkType           = errors.New(\"rtcp: wrong chunk type\")\n\terrBadStructMemberType      = errors.New(\"rtcp: struct contains unexpected member type\")\n\terrBadReadParameter         = errors.New(\"rtcp: cannot read into non-pointer\")\n\terrAppDefinedInvalidLength  = errors.New(\"rtcp: application defined type invalid length\")\n\terrAppDefinedDataTooLarge   = errors.New(\"rtcp: application defined data is too large\")\n\terrAppDefinedInvalidName    = errors.New(\"rtcp: application defined name must be 4 ASCII chars\")\n)\n"
  },
  {
    "path": "extended_report.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"fmt\"\n)\n\n// The ExtendedReport packet is an Implementation of RTCP Extended\n// Reports defined in RFC 3611. It is used to convey detailed\n// information about an RTP stream. Each packet contains one or\n// more report blocks, each of which conveys a different kind of\n// information.\n//\n//\t0                   1                   2                   3\n//\t0 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// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |V=2|P|reserved |   PT=XR=207   |             length            |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                              SSRC                             |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// :                         report blocks                         :\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// .\ntype ExtendedReport struct {\n\tSenderSSRC uint32 `fmt:\"0x%X\"`\n\tReports    []ReportBlock\n}\n\n// ReportBlock represents a single report within an ExtendedReport\n// packet.\ntype ReportBlock interface {\n\tDestinationSSRC() []uint32\n\tsetupBlockHeader()\n\tunpackBlockHeader()\n}\n\n// TypeSpecificField as described in RFC 3611 section 4.5. In typical\n// cases, users of ExtendedReports shouldn't need to access this,\n// and should instead use the corresponding fields in the actual\n// report blocks themselves.\ntype TypeSpecificField uint8\n\n// XRHeader defines the common fields that must appear at the start\n// of each report block. In typical cases, users of ExtendedReports\n// shouldn't need to access this. For locally-constructed report\n// blocks, these values will not be accurate until the corresponding\n// packet is marshaled.\ntype XRHeader struct {\n\tBlockType    BlockTypeType\n\tTypeSpecific TypeSpecificField `fmt:\"0x%X\"`\n\tBlockLength  uint16\n}\n\n// BlockTypeType specifies the type of report in a report block.\ntype BlockTypeType uint8\n\n// Extended Report block types from RFC 3611.\nconst (\n\tLossRLEReportBlockType               = 1 // RFC 3611, section 4.1\n\tDuplicateRLEReportBlockType          = 2 // RFC 3611, section 4.2\n\tPacketReceiptTimesReportBlockType    = 3 // RFC 3611, section 4.3\n\tReceiverReferenceTimeReportBlockType = 4 // RFC 3611, section 4.4\n\tDLRRReportBlockType                  = 5 // RFC 3611, section 4.5\n\tStatisticsSummaryReportBlockType     = 6 // RFC 3611, section 4.6\n\tVoIPMetricsReportBlockType           = 7 // RFC 3611, section 4.7\n)\n\n// String converts the Extended report block types into readable strings.\nfunc (t BlockTypeType) String() string {\n\tswitch t {\n\tcase LossRLEReportBlockType:\n\t\treturn \"LossRLEReportBlockType\"\n\tcase DuplicateRLEReportBlockType:\n\t\treturn \"DuplicateRLEReportBlockType\"\n\tcase PacketReceiptTimesReportBlockType:\n\t\treturn \"PacketReceiptTimesReportBlockType\"\n\tcase ReceiverReferenceTimeReportBlockType:\n\t\treturn \"ReceiverReferenceTimeReportBlockType\"\n\tcase DLRRReportBlockType:\n\t\treturn \"DLRRReportBlockType\"\n\tcase StatisticsSummaryReportBlockType:\n\t\treturn \"StatisticsSummaryReportBlockType\"\n\tcase VoIPMetricsReportBlockType:\n\t\treturn \"VoIPMetricsReportBlockType\"\n\t}\n\n\treturn fmt.Sprintf(\"invalid value %d\", t)\n}\n\n// rleReportBlock defines the common structure used by both\n// Loss RLE report blocks (RFC 3611 §4.1) and Duplicate RLE\n// report blocks (RFC 3611 §4.2).\n//\n//\t0                   1                   2                   3\n//\t0 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// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |  BT = 1 or 2  | rsvd. |   T   |         block length          |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                        SSRC of source                         |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |          begin_seq            |             end_seq           |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |          chunk 1              |             chunk 2           |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// :                              ...                              :\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |          chunk n-1            |             chunk n           |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// .\ntype rleReportBlock struct {\n\tXRHeader\n\tT        uint8  `encoding:\"omit\"`\n\tSSRC     uint32 `fmt:\"0x%X\"`\n\tBeginSeq uint16\n\tEndSeq   uint16\n\tChunks   []Chunk\n}\n\n// Chunk as defined in RFC 3611, section 4.1. These represent information\n// about packet losses and packet duplication. They have three representations:\n//\n// Run Length Chunk:\n//\n//\t 0                   1\n//\t 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5\n//\t+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//\t|C|R|        run length         |\n//\t+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//\n// Bit Vector Chunk:\n//\n//\t 0                   1\n//\t 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5\n//\t+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//\t|C|        bit vector           |\n//\t+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//\n// Terminating Null Chunk:\n//\n//\t 0                   1\n//\t 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5\n//\t+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//\t|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|\n//\t+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\ntype Chunk uint16\n\n// LossRLEReportBlock is used to report information about packet\n// losses, as described in RFC 3611, section 4.1.\ntype LossRLEReportBlock rleReportBlock\n\n// DestinationSSRC returns an array of SSRC values that this report block refers to.\nfunc (b *LossRLEReportBlock) DestinationSSRC() []uint32 {\n\treturn []uint32{b.SSRC}\n}\n\nfunc (b *LossRLEReportBlock) setupBlockHeader() {\n\tb.XRHeader.BlockType = LossRLEReportBlockType\n\tb.XRHeader.TypeSpecific = TypeSpecificField(b.T & 0x0F)\n\tb.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115\n}\n\nfunc (b *LossRLEReportBlock) unpackBlockHeader() {\n\tb.T = uint8(b.XRHeader.TypeSpecific) & 0x0F\n}\n\n// DuplicateRLEReportBlock is used to report information about packet\n// duplication, as described in RFC 3611, section 4.1.\ntype DuplicateRLEReportBlock rleReportBlock\n\n// DestinationSSRC returns an array of SSRC values that this report block refers to.\nfunc (b *DuplicateRLEReportBlock) DestinationSSRC() []uint32 {\n\treturn []uint32{b.SSRC}\n}\n\nfunc (b *DuplicateRLEReportBlock) setupBlockHeader() {\n\tb.XRHeader.BlockType = DuplicateRLEReportBlockType\n\tb.XRHeader.TypeSpecific = TypeSpecificField(b.T & 0x0F)\n\tb.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115\n}\n\nfunc (b *DuplicateRLEReportBlock) unpackBlockHeader() {\n\tb.T = uint8(b.XRHeader.TypeSpecific) & 0x0F\n}\n\n// ChunkType enumerates the three kinds of chunks described in RFC 3611 section 4.1.\ntype ChunkType uint8\n\n// These are the valid values that ChunkType can assume.\nconst (\n\tRunLengthChunkType       = 0\n\tBitVectorChunkType       = 1\n\tTerminatingNullChunkType = 2\n)\n\nfunc (c Chunk) String() string {\n\tswitch c.Type() {\n\tcase RunLengthChunkType:\n\t\trunType, _ := c.RunType()\n\n\t\treturn fmt.Sprintf(\"[RunLength type=%d, length=%d]\", runType, c.Value())\n\tcase BitVectorChunkType:\n\t\treturn fmt.Sprintf(\"[BitVector 0b%015b]\", c.Value())\n\tcase TerminatingNullChunkType:\n\t\treturn \"[TerminatingNull]\"\n\t}\n\n\treturn fmt.Sprintf(\"[0x%X]\", uint16(c))\n}\n\n// Type returns the ChunkType that this Chunk represents.\nfunc (c Chunk) Type() ChunkType {\n\tif c == 0 {\n\t\treturn TerminatingNullChunkType\n\t}\n\n\treturn ChunkType(c >> 15) //nolint:gosec // G115\n}\n\n// RunType returns the RunType that this Chunk represents. It is\n// only valid if ChunkType is RunLengthChunkType.\nfunc (c Chunk) RunType() (uint, error) {\n\tif c.Type() != RunLengthChunkType {\n\t\treturn 0, errWrongChunkType\n\t}\n\n\treturn uint((c >> 14) & 0x01), nil\n}\n\n// Value returns the value represented in this Chunk.\nfunc (c Chunk) Value() uint {\n\tswitch c.Type() {\n\tcase RunLengthChunkType:\n\t\treturn uint(c & 0x3FFF)\n\tcase BitVectorChunkType:\n\t\treturn uint(c & 0x7FFF)\n\tcase TerminatingNullChunkType:\n\t\treturn 0\n\t}\n\n\treturn uint(c)\n}\n\n// PacketReceiptTimesReportBlock represents a Packet Receipt Times\n// report block, as described in RFC 3611 section 4.3.\n//\n//\t0                   1                   2                   3\n//\t0 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// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |     BT=3      | rsvd. |   T   |         block length          |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                        SSRC of source                         |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |          begin_seq            |             end_seq           |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |       Receipt time of packet begin_seq                        |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |       Receipt time of packet (begin_seq + 1) mod 65536        |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// :                              ...                              :\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |       Receipt time of packet (end_seq - 1) mod 65536          |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// .\ntype PacketReceiptTimesReportBlock struct {\n\tXRHeader\n\tT           uint8  `encoding:\"omit\"`\n\tSSRC        uint32 `fmt:\"0x%X\"`\n\tBeginSeq    uint16\n\tEndSeq      uint16\n\tReceiptTime []uint32\n}\n\n// DestinationSSRC returns an array of SSRC values that this report block refers to.\nfunc (b *PacketReceiptTimesReportBlock) DestinationSSRC() []uint32 {\n\treturn []uint32{b.SSRC}\n}\n\nfunc (b *PacketReceiptTimesReportBlock) setupBlockHeader() {\n\tb.XRHeader.BlockType = PacketReceiptTimesReportBlockType\n\tb.XRHeader.TypeSpecific = TypeSpecificField(b.T & 0x0F)\n\tb.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115\n}\n\nfunc (b *PacketReceiptTimesReportBlock) unpackBlockHeader() {\n\tb.T = uint8(b.XRHeader.TypeSpecific) & 0x0F\n}\n\n// ReceiverReferenceTimeReportBlock encodes a Receiver Reference Time\n// report block as described in RFC 3611 section 4.4.\n//\n//\t0                   1                   2                   3\n//\t0 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// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |     BT=4      |   reserved    |       block length = 2        |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |              NTP timestamp, most significant word             |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |             NTP timestamp, least significant word             |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// .\ntype ReceiverReferenceTimeReportBlock struct {\n\tXRHeader\n\tNTPTimestamp uint64\n}\n\n// DestinationSSRC returns an array of SSRC values that this report block refers to.\nfunc (b *ReceiverReferenceTimeReportBlock) DestinationSSRC() []uint32 {\n\treturn []uint32{}\n}\n\nfunc (b *ReceiverReferenceTimeReportBlock) setupBlockHeader() {\n\tb.XRHeader.BlockType = ReceiverReferenceTimeReportBlockType\n\tb.XRHeader.TypeSpecific = 0\n\tb.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115\n}\n\nfunc (b *ReceiverReferenceTimeReportBlock) unpackBlockHeader() {\n}\n\n// DLRRReportBlock encodes a DLRR Report Block as described in\n// RFC 3611 section 4.5.\n//\n//\t0                   1                   2                   3\n//\t0 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// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\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// .\ntype DLRRReportBlock struct {\n\tXRHeader\n\tReports []DLRRReport\n}\n\n// DLRRReport encodes a single report inside a DLRRReportBlock.\ntype DLRRReport struct {\n\tSSRC   uint32 `fmt:\"0x%X\"`\n\tLastRR uint32\n\tDLRR   uint32\n}\n\n// DestinationSSRC returns an array of SSRC values that this report block refers to.\nfunc (b *DLRRReportBlock) DestinationSSRC() []uint32 {\n\tssrc := make([]uint32, len(b.Reports))\n\tfor i, r := range b.Reports {\n\t\tssrc[i] = r.SSRC\n\t}\n\n\treturn ssrc\n}\n\nfunc (b *DLRRReportBlock) setupBlockHeader() {\n\tb.XRHeader.BlockType = DLRRReportBlockType\n\tb.XRHeader.TypeSpecific = 0\n\tb.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115\n}\n\nfunc (b *DLRRReportBlock) unpackBlockHeader() {\n}\n\n// StatisticsSummaryReportBlock encodes a Statistics Summary Report\n// Block as described in RFC 3611, section 4.6.\n//\n//\t0                   1                   2                   3\n//\t0 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// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |     BT=6      |L|D|J|ToH|rsvd.|       block length = 9        |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                        SSRC of source                         |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |          begin_seq            |             end_seq           |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                        lost_packets                           |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                        dup_packets                            |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                         min_jitter                            |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                         max_jitter                            |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                         mean_jitter                           |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                         dev_jitter                            |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// | min_ttl_or_hl | max_ttl_or_hl |mean_ttl_or_hl | dev_ttl_or_hl |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// .\ntype StatisticsSummaryReportBlock struct {\n\tXRHeader\n\tLossReports      bool              `encoding:\"omit\"`\n\tDuplicateReports bool              `encoding:\"omit\"`\n\tJitterReports    bool              `encoding:\"omit\"`\n\tTTLorHopLimit    TTLorHopLimitType `encoding:\"omit\"`\n\tSSRC             uint32            `fmt:\"0x%X\"`\n\tBeginSeq         uint16\n\tEndSeq           uint16\n\tLostPackets      uint32\n\tDupPackets       uint32\n\tMinJitter        uint32\n\tMaxJitter        uint32\n\tMeanJitter       uint32\n\tDevJitter        uint32\n\tMinTTLOrHL       uint8\n\tMaxTTLOrHL       uint8\n\tMeanTTLOrHL      uint8\n\tDevTTLOrHL       uint8\n}\n\n// TTLorHopLimitType encodes values for the ToH field in\n// a StatisticsSummaryReportBlock.\ntype TTLorHopLimitType uint8\n\n// Values for TTLorHopLimitType.\nconst (\n\tToHMissing = 0\n\tToHIPv4    = 1\n\tToHIPv6    = 2\n)\n\nfunc (t TTLorHopLimitType) String() string {\n\tswitch t {\n\tcase ToHMissing:\n\t\treturn \"[ToH Missing]\"\n\tcase ToHIPv4:\n\t\treturn \"[ToH = IPv4]\"\n\tcase ToHIPv6:\n\t\treturn \"[ToH = IPv6]\"\n\t}\n\n\treturn \"[ToH Flag is Invalid]\"\n}\n\n// DestinationSSRC returns an array of SSRC values that this report block refers to.\nfunc (b *StatisticsSummaryReportBlock) DestinationSSRC() []uint32 {\n\treturn []uint32{b.SSRC}\n}\n\nfunc (b *StatisticsSummaryReportBlock) setupBlockHeader() {\n\tb.XRHeader.BlockType = StatisticsSummaryReportBlockType\n\tb.XRHeader.TypeSpecific = 0x00\n\tif b.LossReports {\n\t\tb.XRHeader.TypeSpecific |= 0x80\n\t}\n\tif b.DuplicateReports {\n\t\tb.XRHeader.TypeSpecific |= 0x40\n\t}\n\tif b.JitterReports {\n\t\tb.XRHeader.TypeSpecific |= 0x20\n\t}\n\tb.XRHeader.TypeSpecific |= TypeSpecificField((b.TTLorHopLimit & 0x03) << 3)\n\tb.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115\n}\n\nfunc (b *StatisticsSummaryReportBlock) unpackBlockHeader() {\n\tb.LossReports = b.XRHeader.TypeSpecific&0x80 != 0\n\tb.DuplicateReports = b.XRHeader.TypeSpecific&0x40 != 0\n\tb.JitterReports = b.XRHeader.TypeSpecific&0x20 != 0\n\tb.TTLorHopLimit = TTLorHopLimitType((b.XRHeader.TypeSpecific & 0x18) >> 3)\n}\n\n// VoIPMetricsReportBlock encodes a VoIP Metrics Report Block as described\n// in RFC 3611, section 4.7.\n//\n//\t0                   1                   2                   3\n//\t0 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// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |     BT=7      |   reserved    |       block length = 8        |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                        SSRC of source                         |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |   loss rate   | discard rate  | burst density |  gap density  |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |       burst duration          |         gap duration          |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |     round trip delay          |       end system delay        |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// | signal level  |  noise level  |     RERL      |     Gmin      |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |   R factor    | ext. R factor |    MOS-LQ     |    MOS-CQ     |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |   RX config   |   reserved    |          JB nominal           |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |          JB maximum           |          JB abs max           |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// .\ntype VoIPMetricsReportBlock struct {\n\tXRHeader\n\tSSRC           uint32 `fmt:\"0x%X\"`\n\tLossRate       uint8\n\tDiscardRate    uint8\n\tBurstDensity   uint8\n\tGapDensity     uint8\n\tBurstDuration  uint16\n\tGapDuration    uint16\n\tRoundTripDelay uint16\n\tEndSystemDelay uint16\n\tSignalLevel    uint8\n\tNoiseLevel     uint8\n\tRERL           uint8\n\tGmin           uint8\n\tRFactor        uint8\n\tExtRFactor     uint8\n\tMOSLQ          uint8\n\tMOSCQ          uint8\n\tRXConfig       uint8\n\t_              uint8\n\tJBNominal      uint16\n\tJBMaximum      uint16\n\tJBAbsMax       uint16\n}\n\n// DestinationSSRC returns an array of SSRC values that this report block refers to.\nfunc (b *VoIPMetricsReportBlock) DestinationSSRC() []uint32 {\n\treturn []uint32{b.SSRC}\n}\n\nfunc (b *VoIPMetricsReportBlock) setupBlockHeader() {\n\tb.XRHeader.BlockType = VoIPMetricsReportBlockType\n\tb.XRHeader.TypeSpecific = 0\n\tb.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115\n}\n\nfunc (b *VoIPMetricsReportBlock) unpackBlockHeader() {\n}\n\n// UnknownReportBlock is used to store bytes for any report block\n// that has an unknown Report Block Type.\ntype UnknownReportBlock struct {\n\tXRHeader\n\tBytes []byte\n}\n\n// DestinationSSRC returns an array of SSRC values that this report block refers to.\nfunc (b *UnknownReportBlock) DestinationSSRC() []uint32 {\n\treturn []uint32{}\n}\n\nfunc (b *UnknownReportBlock) setupBlockHeader() {\n\tb.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115\n}\n\nfunc (b *UnknownReportBlock) unpackBlockHeader() {\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (x ExtendedReport) MarshalSize() int {\n\treturn wireSize(x)\n}\n\n// Marshal encodes the ExtendedReport in binary.\nfunc (x ExtendedReport) Marshal() ([]byte, error) {\n\tfor _, p := range x.Reports {\n\t\tp.setupBlockHeader()\n\t}\n\n\tlength := wireSize(x)\n\n\t// RTCP Header\n\theader := Header{\n\t\tType:   TypeExtendedReport,\n\t\tLength: uint16(length / 4), //nolint:gosec // G115\n\t}\n\theaderBuffer, err := header.Marshal()\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\tlength += len(headerBuffer)\n\n\trawPacket := make([]byte, length)\n\tbuffer := packetBuffer{bytes: rawPacket}\n\n\terr = buffer.write(headerBuffer)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\terr = buffer.write(x)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the ExtendedReport from binary.\n//\n//nolint:cyclop\nfunc (x *ExtendedReport) Unmarshal(b []byte) error {\n\tvar header Header\n\tif err := header.Unmarshal(b); err != nil {\n\t\treturn err\n\t}\n\tif header.Type != TypeExtendedReport {\n\t\treturn errWrongType\n\t}\n\n\tbuffer := packetBuffer{bytes: b[headerLength:]}\n\terr := buffer.read(&x.SenderSSRC)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor len(buffer.bytes) > 0 {\n\t\tvar block ReportBlock\n\n\t\theaderBuffer := buffer\n\t\txrHeader := XRHeader{}\n\t\terr = headerBuffer.read(&xrHeader)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tswitch xrHeader.BlockType {\n\t\tcase LossRLEReportBlockType:\n\t\t\tblock = new(LossRLEReportBlock)\n\t\tcase DuplicateRLEReportBlockType:\n\t\t\tblock = new(DuplicateRLEReportBlock)\n\t\tcase PacketReceiptTimesReportBlockType:\n\t\t\tblock = new(PacketReceiptTimesReportBlock)\n\t\tcase ReceiverReferenceTimeReportBlockType:\n\t\t\tblock = new(ReceiverReferenceTimeReportBlock)\n\t\tcase DLRRReportBlockType:\n\t\t\tblock = new(DLRRReportBlock)\n\t\tcase StatisticsSummaryReportBlockType:\n\t\t\tblock = new(StatisticsSummaryReportBlock)\n\t\tcase VoIPMetricsReportBlockType:\n\t\t\tblock = new(VoIPMetricsReportBlock)\n\t\tdefault:\n\t\t\tblock = new(UnknownReportBlock)\n\t\t}\n\n\t\t// We need to limit the amount of data available to\n\t\t// this block to the actual length of the block\n\t\tblockLength := (int(xrHeader.BlockLength) + 1) * 4\n\t\tblockBuffer := buffer.split(blockLength)\n\t\terr = blockBuffer.read(block)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tblock.unpackBlockHeader()\n\t\tx.Reports = append(x.Reports, block)\n\t}\n\n\treturn nil\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (x *ExtendedReport) DestinationSSRC() []uint32 {\n\tssrc := make([]uint32, 0, len(x.Reports)+1)\n\tssrc = append(ssrc, x.SenderSSRC)\n\tfor _, p := range x.Reports {\n\t\tssrc = append(ssrc, p.DestinationSSRC()...)\n\t}\n\n\treturn ssrc\n}\n\nfunc (x *ExtendedReport) String() string {\n\treturn stringify(x)\n}\n"
  },
  {
    "path": "extended_report_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// Assert that ExtendedReport is a Packet.\nvar _ Packet = (*ExtendedReport)(nil)\n\n// Assert that all the extended report blocks implement the interface.\nvar (\n\t_ ReportBlock = (*LossRLEReportBlock)(nil)\n\t_ ReportBlock = (*DuplicateRLEReportBlock)(nil)\n\t_ ReportBlock = (*PacketReceiptTimesReportBlock)(nil)\n\t_ ReportBlock = (*ReceiverReferenceTimeReportBlock)(nil)\n\t_ ReportBlock = (*DLRRReportBlock)(nil)\n\t_ ReportBlock = (*StatisticsSummaryReportBlock)(nil)\n\t_ ReportBlock = (*VoIPMetricsReportBlock)(nil)\n\t_ ReportBlock = (*UnknownReportBlock)(nil)\n)\n\nfunc testPacket() Packet {\n\treturn &ExtendedReport{\n\t\tSenderSSRC: 0x01020304,\n\t\tReports: []ReportBlock{\n\t\t\t&LossRLEReportBlock{\n\t\t\t\tXRHeader: XRHeader{\n\t\t\t\t\tBlockType: LossRLEReportBlockType,\n\t\t\t\t},\n\t\t\t\tT:        12,\n\t\t\t\tSSRC:     0x12345689,\n\t\t\t\tBeginSeq: 5,\n\t\t\t\tEndSeq:   12,\n\t\t\t\tChunks: []Chunk{\n\t\t\t\t\tChunk(0x4006),\n\t\t\t\t\tChunk(0x0006),\n\t\t\t\t\tChunk(0x8765),\n\t\t\t\t\tChunk(0x0000),\n\t\t\t\t},\n\t\t\t},\n\t\t\t&DuplicateRLEReportBlock{\n\t\t\t\tXRHeader: XRHeader{\n\t\t\t\t\tBlockType: DuplicateRLEReportBlockType,\n\t\t\t\t},\n\t\t\t\tT:        6,\n\t\t\t\tSSRC:     0x12345689,\n\t\t\t\tBeginSeq: 5,\n\t\t\t\tEndSeq:   12,\n\t\t\t\tChunks: []Chunk{\n\t\t\t\t\tChunk(0x4123),\n\t\t\t\t\tChunk(0x3FFF),\n\t\t\t\t\tChunk(0xFFFF),\n\t\t\t\t\tChunk(0x0000),\n\t\t\t\t},\n\t\t\t},\n\t\t\t&PacketReceiptTimesReportBlock{\n\t\t\t\tXRHeader: XRHeader{\n\t\t\t\t\tBlockType: PacketReceiptTimesReportBlockType,\n\t\t\t\t},\n\t\t\t\tT:        3,\n\t\t\t\tSSRC:     0x98765432,\n\t\t\t\tBeginSeq: 15432,\n\t\t\t\tEndSeq:   15577,\n\t\t\t\tReceiptTime: []uint32{\n\t\t\t\t\t0x11111111,\n\t\t\t\t\t0x22222222,\n\t\t\t\t\t0x33333333,\n\t\t\t\t\t0x44444444,\n\t\t\t\t\t0x55555555,\n\t\t\t\t},\n\t\t\t},\n\t\t\t&ReceiverReferenceTimeReportBlock{\n\t\t\t\tXRHeader: XRHeader{\n\t\t\t\t\tBlockType: ReceiverReferenceTimeReportBlockType,\n\t\t\t\t},\n\t\t\t\tNTPTimestamp: 0x0102030405060708,\n\t\t\t},\n\t\t\t&DLRRReportBlock{\n\t\t\t\tXRHeader: XRHeader{\n\t\t\t\t\tBlockType: DLRRReportBlockType,\n\t\t\t\t},\n\t\t\t\tReports: []DLRRReport{\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:   0x88888888,\n\t\t\t\t\t\tLastRR: 0x12345678,\n\t\t\t\t\t\tDLRR:   0x99999999,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:   0x09090909,\n\t\t\t\t\t\tLastRR: 0x12345678,\n\t\t\t\t\t\tDLRR:   0x99999999,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:   0x11223344,\n\t\t\t\t\t\tLastRR: 0x12345678,\n\t\t\t\t\t\tDLRR:   0x99999999,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t&StatisticsSummaryReportBlock{\n\t\t\t\tXRHeader{\n\t\t\t\t\tBlockType: StatisticsSummaryReportBlockType,\n\t\t\t\t},\n\t\t\t\ttrue, true, true, ToHIPv4,\n\t\t\t\t0xFEDCBA98,\n\t\t\t\t0x1234, 0x5678,\n\t\t\t\t0x11111111,\n\t\t\t\t0x22222222,\n\t\t\t\t0x33333333,\n\t\t\t\t0x44444444,\n\t\t\t\t0x55555555,\n\t\t\t\t0x66666666,\n\t\t\t\t0x01, 0x02, 0x03, 0x04,\n\t\t\t},\n\t\t\t&VoIPMetricsReportBlock{\n\t\t\t\tXRHeader{\n\t\t\t\t\tBlockType: VoIPMetricsReportBlockType,\n\t\t\t\t},\n\t\t\t\t0x89ABCDEF,\n\t\t\t\t0x05, 0x06, 0x07, 0x08,\n\t\t\t\t0x1111, 0x2222, 0x3333, 0x4444,\n\t\t\t\t0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,\n\t\t\t\t0x00,\n\t\t\t\t0x1122, 0x3344, 0x5566,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc encodedPacket() []byte {\n\treturn []byte{\n\t\t// RTP Header\n\t\t0x80, 0xCF, 0x00, 0x33, // byte 0 - 3\n\t\t// Sender SSRC\n\t\t0x01, 0x02, 0x03, 0x04,\n\t\t// Loss RLE Report Block\n\t\t0x01, 0x0C, 0x00, 0x04, // byte 8 - 11\n\t\t// Source SSRC\n\t\t0x12, 0x34, 0x56, 0x89,\n\t\t// Begin & End Seq\n\t\t0x00, 0x05, 0x00, 0x0C, // byte 16 - 19\n\t\t// Chunks\n\t\t0x40, 0x06, 0x00, 0x06,\n\t\t0x87, 0x65, 0x00, 0x00, // byte 24 - 27\n\t\t// Duplicate RLE Report Block\n\t\t0x02, 0x06, 0x00, 0x04,\n\t\t// Source SSRC\n\t\t0x12, 0x34, 0x56, 0x89, // byte 32 - 35\n\t\t// Begin & End Seq\n\t\t0x00, 0x05, 0x00, 0x0C,\n\t\t// Chunks\n\t\t0x41, 0x23, 0x3F, 0xFF, // byte 40 - 43\n\t\t0xFF, 0xFF, 0x00, 0x00,\n\t\t// Packet Receipt Times Report Block\n\t\t0x03, 0x03, 0x00, 0x07, // byte 48 - 51\n\t\t// Source SSRC\n\t\t0x98, 0x76, 0x54, 0x32,\n\t\t// Begin & End Seq\n\t\t0x3C, 0x48, 0x3C, 0xD9, // byte 56 - 59\n\t\t// Receipt times\n\t\t0x11, 0x11, 0x11, 0x11,\n\t\t0x22, 0x22, 0x22, 0x22, // byte 64 - 67\n\t\t0x33, 0x33, 0x33, 0x33,\n\t\t0x44, 0x44, 0x44, 0x44, // byte 72 - 75\n\t\t0x55, 0x55, 0x55, 0x55,\n\t\t// Receiver Reference Time Report\n\t\t0x04, 0x00, 0x00, 0x02, // byte 80 - 83\n\t\t// Timestamp\n\t\t0x01, 0x02, 0x03, 0x04,\n\t\t0x05, 0x06, 0x07, 0x08, // byte 88 - 91\n\t\t// DLRR Report\n\t\t0x05, 0x00, 0x00, 0x09,\n\t\t// SSRC 1\n\t\t0x88, 0x88, 0x88, 0x88, // byte 96 - 99\n\t\t// LastRR 1\n\t\t0x12, 0x34, 0x56, 0x78,\n\t\t// DLRR 1\n\t\t0x99, 0x99, 0x99, 0x99, // byte 104 - 107\n\t\t// SSRC 2\n\t\t0x09, 0x09, 0x09, 0x09,\n\t\t// LastRR 2\n\t\t0x12, 0x34, 0x56, 0x78, // byte 112 - 115\n\t\t// DLRR 2\n\t\t0x99, 0x99, 0x99, 0x99,\n\t\t// SSRC 3\n\t\t0x11, 0x22, 0x33, 0x44, // byte 120 - 123\n\t\t// LastRR 3\n\t\t0x12, 0x34, 0x56, 0x78,\n\t\t// DLRR 3\n\t\t0x99, 0x99, 0x99, 0x99, // byte 128 - 131\n\t\t// Statistics Summary Report\n\t\t0x06, 0xE8, 0x00, 0x09,\n\t\t// SSRC\n\t\t0xFE, 0xDC, 0xBA, 0x98, // byte 136 - 139\n\t\t// Various statistics\n\t\t0x12, 0x34, 0x56, 0x78,\n\t\t0x11, 0x11, 0x11, 0x11, // byte 144 - 147\n\t\t0x22, 0x22, 0x22, 0x22,\n\t\t0x33, 0x33, 0x33, 0x33, // byte 152 - 155\n\t\t0x44, 0x44, 0x44, 0x44,\n\t\t0x55, 0x55, 0x55, 0x55, // byte 160 - 163\n\t\t0x66, 0x66, 0x66, 0x66,\n\t\t0x01, 0x02, 0x03, 0x04, // byte 168 - 171\n\t\t// VoIP Metrics Report\n\t\t0x07, 0x00, 0x00, 0x08,\n\t\t// SSRC\n\t\t0x89, 0xAB, 0xCD, 0xEF, // byte 176 - 179\n\t\t// Various statistics\n\t\t0x05, 0x06, 0x07, 0x08,\n\t\t0x11, 0x11, 0x22, 0x22, // byte 184 - 187\n\t\t0x33, 0x33, 0x44, 0x44,\n\t\t0x11, 0x22, 0x33, 0x44, // byte 192 - 195\n\t\t0x55, 0x66, 0x77, 0x88,\n\t\t0x99, 0x00, 0x11, 0x22, // byte 200 - 203\n\t\t0x33, 0x44, 0x55, 0x66, // byte 204 - 207\n\t}\n}\n\nfunc TestEncode(t *testing.T) {\n\texpected := encodedPacket()\n\tpacket := testPacket()\n\trawPacket, err := packet.Marshal()\n\tassert.NoError(t, err)\n\tassert.Equal(t, len(expected), len(rawPacket), \"Encoded message length does not match expected length\")\n\n\tfor i := range rawPacket {\n\t\tassert.Equalf(t, expected[i], rawPacket[i], \"Byte %d of encoded packet does not match\", i)\n\t}\n}\n\nfunc TestDecode(t *testing.T) {\n\tencoded := encodedPacket()\n\texpected := testPacket()\n\n\t// We need to make sure the header has been set up correctly\n\t// before we test for equality\n\textendedReports, ok := expected.(*ExtendedReport)\n\tassert.True(t, ok)\n\n\tfor _, p := range extendedReports.Reports {\n\t\tp.setupBlockHeader()\n\t}\n\n\treport := new(ExtendedReport)\n\terr := report.Unmarshal(encoded)\n\tassert.NoError(t, err)\n\tassert.Equal(t, expected, report)\n\n\tpktStringer, ok := expected.(fmt.Stringer)\n\tassert.True(t, ok)\n\tassert.Equal(t, report.String(), pktStringer.String(), \"Decoded packet does not match expected packet\")\n\n\tvar includeSenderSSRC bool\n\tfor _, ssrc := range report.DestinationSSRC() {\n\t\tif ssrc == report.SenderSSRC {\n\t\t\tincludeSenderSSRC = true\n\t\t}\n\t}\n\tassert.True(t, includeSenderSSRC, \"DestinationSSRC does not include the SenderSSRC\")\n}\n"
  },
  {
    "path": "full_intra_request.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"strings\"\n)\n\n// A FIREntry is a (SSRC, seqno) pair, as carried by FullIntraRequest.\ntype FIREntry struct {\n\tSSRC           uint32\n\tSequenceNumber uint8\n}\n\n// The FullIntraRequest packet is used to reliably request an Intra frame\n// in a video stream.  See RFC 5104 Section 3.5.1.  This is not for loss\n// recovery, which should use PictureLossIndication (PLI) instead.\ntype FullIntraRequest struct {\n\tSenderSSRC uint32\n\tMediaSSRC  uint32\n\n\tFIR []FIREntry\n}\n\nconst (\n\tfirOffset = 8\n)\n\nvar _ Packet = (*FullIntraRequest)(nil)\n\n// Marshal encodes the FullIntraRequest.\nfunc (p FullIntraRequest) Marshal() ([]byte, error) {\n\trawPacket := make([]byte, firOffset+(len(p.FIR)*8))\n\tbinary.BigEndian.PutUint32(rawPacket, p.SenderSSRC)\n\tbinary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC)\n\tfor i, fir := range p.FIR {\n\t\tbinary.BigEndian.PutUint32(rawPacket[firOffset+8*i:], fir.SSRC)\n\t\trawPacket[firOffset+8*i+4] = fir.SequenceNumber\n\t}\n\th := p.Header()\n\thData, err := h.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn append(hData, rawPacket...), nil\n}\n\n// Unmarshal decodes the TransportLayerNack.\nfunc (p *FullIntraRequest) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) < (headerLength + ssrcLength) {\n\t\treturn errPacketTooShort\n\t}\n\n\tvar header Header\n\tif err := header.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\n\tif len(rawPacket) < (headerLength + int(4*header.Length)) {\n\t\treturn errPacketTooShort\n\t}\n\n\tif header.Type != TypePayloadSpecificFeedback || header.Count != FormatFIR {\n\t\treturn errWrongType\n\t}\n\n\t// The FCI field MUST contain one or more FIR entries\n\tif 4*header.Length-firOffset <= 0 || (4*header.Length)%8 != 0 {\n\t\treturn errBadLength\n\t}\n\n\tp.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])\n\tp.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])\n\tfor i := headerLength + firOffset; i < (headerLength + int(header.Length*4)); i += 8 {\n\t\tp.FIR = append(p.FIR, FIREntry{\n\t\t\tbinary.BigEndian.Uint32(rawPacket[i:]),\n\t\t\trawPacket[i+4],\n\t\t})\n\t}\n\n\treturn nil\n}\n\n// Header returns the Header associated with this packet.\nfunc (p *FullIntraRequest) Header() Header {\n\treturn Header{\n\t\tCount:  FormatFIR,\n\t\tType:   TypePayloadSpecificFeedback,\n\t\tLength: uint16((p.MarshalSize() / 4) - 1), //nolint:gosec // G115\n\t}\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (p *FullIntraRequest) MarshalSize() int {\n\treturn headerLength + firOffset + len(p.FIR)*8\n}\n\nfunc (p *FullIntraRequest) String() string {\n\tvar out strings.Builder\n\tfmt.Fprintf(&out, \"FullIntraRequest %x %x\",\n\t\tp.SenderSSRC, p.MediaSSRC)\n\tfor _, e := range p.FIR {\n\t\tfmt.Fprintf(&out, \" (%x %v)\", e.SSRC, e.SequenceNumber)\n\t}\n\n\treturn out.String()\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (p *FullIntraRequest) DestinationSSRC() []uint32 {\n\tssrcs := make([]uint32, 0, len(p.FIR))\n\tfor _, entry := range p.FIR {\n\t\tssrcs = append(ssrcs, entry.SSRC)\n\t}\n\n\treturn ssrcs\n}\n"
  },
  {
    "path": "full_intra_request_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestFullIntraRequestUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      FullIntraRequest\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, FMT=4, PSFB, len=4\n\t\t\t\t0x84, 0xce, 0x00, 0x04,\n\t\t\t\t// ssrc=0x0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x4bc4fcb4\n\t\t\t\t0x4b, 0xc4, 0xfc, 0xb4,\n\t\t\t\t// ssrc=0x12345678\n\t\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t\t// Seqno=0x42\n\t\t\t\t0x42, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWant: FullIntraRequest{\n\t\t\t\tSenderSSRC: 0x0,\n\t\t\t\tMediaSSRC:  0x4bc4fcb4,\n\t\t\t\tFIR: []FIREntry{\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:           0x12345678,\n\t\t\t\t\t\tSequenceNumber: 0x42,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"also valid\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, FMT=4, PSFB, len=6\n\t\t\t\t0x84, 0xce, 0x00, 0x06,\n\t\t\t\t// ssrc=0x0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x4bc4fcb4\n\t\t\t\t0x4b, 0xc4, 0xfc, 0xb4,\n\t\t\t\t// ssrc=0x12345678\n\t\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t\t// Seqno=0x42\n\t\t\t\t0x42, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x98765432\n\t\t\t\t0x98, 0x76, 0x54, 0x32,\n\t\t\t\t// Seqno=0x57\n\t\t\t\t0x57, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWant: FullIntraRequest{\n\t\t\t\tSenderSSRC: 0x0,\n\t\t\t\tMediaSSRC:  0x4bc4fcb4,\n\t\t\t\tFIR: []FIREntry{\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:           0x12345678,\n\t\t\t\t\t\tSequenceNumber: 0x42,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:           0x98765432,\n\t\t\t\t\t\tSequenceNumber: 0x57,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"packet too short\",\n\t\t\tData: []byte{\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"invalid header\",\n\t\t\tData: []byte{\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWantError: errBadVersion,\n\t\t},\n\t\t{\n\t\t\tName: \"wrong type\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, FMT=4, RR, len=4\n\t\t\t\t0x84, 0xc9, 0x00, 0x04,\n\t\t\t\t// ssrc=0x0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x4bc4fcb4\n\t\t\t\t0x4b, 0xc4, 0xfc, 0xb4,\n\t\t\t\t// ssrc=0x12345678\n\t\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t\t// Seqno=0x42\n\t\t\t\t0x42, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t\t{\n\t\t\tName: \"wrong fmt\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, FMT=2, PSFB, len=4\n\t\t\t\t0x82, 0xce, 0x00, 0x04,\n\t\t\t\t// ssrc=0x0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x4bc4fcb4\n\t\t\t\t0x4b, 0xc4, 0xfc, 0xb4,\n\t\t\t\t// ssrc=0x12345678\n\t\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t\t// Seqno=0x42\n\t\t\t\t0x42, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t\t{\n\t\t\tName: \"wrong length\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, FMT=4, PSFB, len=3\n\t\t\t\t0x84, 0xce, 0x00, 0x03,\n\t\t\t\t// ssrc=0x0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x4bc4fcb4\n\t\t\t\t0x4b, 0xc4, 0xfc, 0xb4,\n\t\t\t\t// ssrc=0x12345678\n\t\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t},\n\t\t\tWantError: errBadLength,\n\t\t},\n\t} {\n\t\tvar fir FullIntraRequest\n\t\terr := fir.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q rr mismatch\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equalf(t, test.Want, fir, \"Unmarshal %q rr mismatch\", test.Name)\n\t}\n}\n\nfunc TestFullIntraRequestRoundTrip(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tPacket    FullIntraRequest\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tPacket: FullIntraRequest{\n\t\t\t\tSenderSSRC: 1,\n\t\t\t\tMediaSSRC:  2,\n\t\t\t\tFIR: []FIREntry{{\n\t\t\t\t\tSSRC:           3,\n\t\t\t\t\tSequenceNumber: 42,\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"also valid\",\n\t\t\tPacket: FullIntraRequest{\n\t\t\t\tSenderSSRC: 5000,\n\t\t\t\tMediaSSRC:  6000,\n\t\t\t\tFIR: []FIREntry{{\n\t\t\t\t\tSSRC:           3,\n\t\t\t\t\tSequenceNumber: 57,\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t} {\n\t\tdata, err := test.Packet.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar decoded FullIntraRequest\n\t\tassert.NoErrorf(t, decoded.Unmarshal(data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Packet, decoded, \"%q rr header mismatch\", test.Name)\n\t}\n}\n\nfunc TestFullIntraRequestUnmarshalHeader(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      Header\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid header\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, FMT=1, PSFB, len=4\n\t\t\t\t0x84, 0xce, 0x00, 0x04,\n\t\t\t\t// ssrc=0x0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x4bc4fcb4\n\t\t\t\t0x4b, 0xc4, 0xfc, 0xb4,\n\t\t\t\t// ssrc=0x00000000\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// Seqno=0x22\n\t\t\t\t0x22, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWant: Header{\n\t\t\t\tCount:  FormatFIR,\n\t\t\t\tType:   TypePayloadSpecificFeedback,\n\t\t\t\tLength: 4,\n\t\t\t},\n\t\t},\n\t} {\n\t\tvar fir FullIntraRequest\n\t\terr := fir.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal header %q rr mismatch\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equalf(t, test.Want, fir.Header(), \"Unmarshal header %q rr mismatch\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "fuzz_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n)\n\nfunc FuzzUnmarshal(f *testing.F) {\n\tf.Add([]byte{})\n\n\tf.Fuzz(func(_ *testing.T, data []byte) {\n\t\tpackets, err := Unmarshal(data)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tfor _, packet := range packets {\n\t\t\t_, err = packet.Marshal()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/pion/rtcp\n\ngo 1.24.0\n\nrequire github.com/stretchr/testify v1.11.1\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "goodbye.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"strings\"\n)\n\n// The Goodbye packet indicates that one or more sources are no longer active.\ntype Goodbye struct {\n\t// The SSRC/CSRC identifiers that are no longer active\n\tSources []uint32\n\t// Optional text indicating the reason for leaving, e.g., \"camera malfunction\" or \"RTP loop detected\"\n\tReason string\n}\n\n// Marshal encodes the Goodbye packet in binary.\nfunc (g Goodbye) Marshal() ([]byte, error) {\n\t/*\n\t *        0                   1                   2                   3\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 *       |V=2|P|    SC   |   PT=BYE=203  |             length            |\n\t *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *       |                           SSRC/CSRC                           |\n\t *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *       :                              ...                              :\n\t *       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * (opt) |     length    |               reason for leaving            ...\n\t *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\n\trawPacket := make([]byte, g.MarshalSize())\n\tpacketBody := rawPacket[headerLength:]\n\n\tif len(g.Sources) > countMax {\n\t\treturn nil, errTooManySources\n\t}\n\n\tfor i, s := range g.Sources {\n\t\tbinary.BigEndian.PutUint32(packetBody[i*ssrcLength:], s)\n\t}\n\n\tif g.Reason != \"\" {\n\t\treason := []byte(g.Reason)\n\n\t\tif len(reason) > sdesMaxOctetCount {\n\t\t\treturn nil, errReasonTooLong\n\t\t}\n\n\t\treasonOffset := len(g.Sources) * ssrcLength\n\t\tpacketBody[reasonOffset] = uint8(len(reason)) //nolint:gosec // G115\n\t\tcopy(packetBody[reasonOffset+1:], reason)\n\t}\n\n\thData, err := g.Header().Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcopy(rawPacket, hData)\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the Goodbye packet from binary.\nfunc (g *Goodbye) Unmarshal(rawPacket []byte) error {\n\t/*\n\t *        0                   1                   2                   3\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 *       |V=2|P|    SC   |   PT=BYE=203  |             length            |\n\t *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *       |                           SSRC/CSRC                           |\n\t *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *       :                              ...                              :\n\t *       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * (opt) |     length    |               reason for leaving            ...\n\t *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\n\tvar header Header\n\tif err := header.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\n\tif header.Type != TypeGoodbye {\n\t\treturn errWrongType\n\t}\n\n\tif getPadding(len(rawPacket)) != 0 {\n\t\treturn errPacketTooShort\n\t}\n\n\tg.Sources = make([]uint32, header.Count)\n\n\treasonOffset := int(headerLength + header.Count*ssrcLength)\n\tif reasonOffset > len(rawPacket) {\n\t\treturn errPacketTooShort\n\t}\n\n\tfor i := 0; i < int(header.Count); i++ {\n\t\toffset := headerLength + i*ssrcLength\n\n\t\tg.Sources[i] = binary.BigEndian.Uint32(rawPacket[offset:])\n\t}\n\n\tif reasonOffset < len(rawPacket) {\n\t\treasonLen := int(rawPacket[reasonOffset])\n\t\treasonEnd := reasonOffset + 1 + reasonLen\n\n\t\tif reasonEnd > len(rawPacket) {\n\t\t\treturn errPacketTooShort\n\t\t}\n\n\t\tg.Reason = string(rawPacket[reasonOffset+1 : reasonEnd])\n\t}\n\n\treturn nil\n}\n\n// Header returns the Header associated with this packet.\nfunc (g *Goodbye) Header() Header {\n\treturn Header{\n\t\tPadding: false,\n\t\tCount:   uint8(len(g.Sources)), //nolint:gosec //G115\n\t\tType:    TypeGoodbye,\n\t\tLength:  uint16((g.MarshalSize() / 4) - 1), //nolint:gosec //G115\n\t}\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (g *Goodbye) MarshalSize() int {\n\tsrcsLength := len(g.Sources) * ssrcLength\n\t// reason is optional\n\treasonLength := len(g.Reason)\n\tif reasonLength > 0 {\n\t\treasonLength++\n\t}\n\n\tl := headerLength + srcsLength + reasonLength\n\n\t// align to 32-bit boundary\n\treturn l + getPadding(l)\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (g *Goodbye) DestinationSSRC() []uint32 {\n\tout := make([]uint32, len(g.Sources))\n\tcopy(out, g.Sources)\n\n\treturn out\n}\n\nfunc (g Goodbye) String() string {\n\tvar out strings.Builder\n\tout.WriteString(\"Goodbye\\n\")\n\tfor i, s := range g.Sources {\n\t\tfmt.Fprintf(&out, \"\\tSource %d: %x\\n\", i, s)\n\t}\n\tfmt.Fprintf(&out, \"\\tReason: %s\\n\", g.Reason)\n\n\treturn out.String()\n}\n"
  },
  {
    "path": "goodbye_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*Goodbye)(nil) // assert is a Packet\n\nfunc TestGoodbyeUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      Goodbye\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, BYE, len=12\n\t\t\t\t0x81, 0xcb, 0x00, 0x0c,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// len=3, text=FOO\n\t\t\t\t0x03, 0x46, 0x4f, 0x4f,\n\t\t\t},\n\t\t\tWant: Goodbye{\n\t\t\t\tSources: []uint32{0x902f9e2e},\n\t\t\t\tReason:  \"FOO\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"invalid octet count\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, BYE, len=12\n\t\t\t\t0x81, 0xcb, 0x00, 0x0c,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// len=4, text=FOO\n\t\t\t\t0x04, 0x46, 0x4f, 0x4f,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"wrong type\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=12\n\t\t\t\t0x81, 0xca, 0x00, 0x0c,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// len=3, text=FOO\n\t\t\t\t0x03, 0x46, 0x4f, 0x4f,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t\t{\n\t\t\tName: \"short reason\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, BYE, len=12\n\t\t\t\t0x81, 0xcb, 0x00, 0x0c,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// len=3, text=F + padding\n\t\t\t\t0x01, 0x46, 0x00, 0x00,\n\t\t\t},\n\t\t\tWant: Goodbye{\n\t\t\t\tSources: []uint32{0x902f9e2e},\n\t\t\t\tReason:  \"F\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"not byte aligned\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, BYE, len=10\n\t\t\t\t0x81, 0xcb, 0x00, 0x0a,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// len=1, text=F\n\t\t\t\t0x01, 0x46,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"bad count in header\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=2, BYE, len=8\n\t\t\t\t0x82, 0xcb, 0x00, 0x0c,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"empty packet\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=0, BYE, len=4\n\t\t\t\t0x80, 0xcb, 0x00, 0x04,\n\t\t\t},\n\t\t\tWant: Goodbye{\n\t\t\t\tSources: []uint32{},\n\t\t\t\tReason:  \"\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:      \"nil\",\n\t\t\tData:      nil,\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t} {\n\t\tvar bye Goodbye\n\t\terr := bye.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q bye mismatch\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equalf(t, test.Want, bye, \"Unmarshal %q bye mismatch\", test.Name)\n\t}\n}\n\nfunc TestGoodbyeRoundTrip(t *testing.T) {\n\t// a slice with enough sources to overflow an 5-bit int\n\tvar tooManySources []uint32\n\tvar tooLongText strings.Builder\n\n\tfor range 1 << 5 {\n\t\ttooManySources = append(tooManySources, 0x00)\n\t}\n\tfor range 1 << 8 {\n\t\ttooLongText.WriteString(\"x\")\n\t}\n\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tBye       Goodbye\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"empty\",\n\t\t\tBye: Goodbye{\n\t\t\t\tSources: []uint32{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tBye: Goodbye{\n\t\t\t\tSources: []uint32{\n\t\t\t\t\t0x01020304,\n\t\t\t\t\t0x05060708,\n\t\t\t\t},\n\t\t\t\tReason: \"because\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"empty reason\",\n\t\t\tBye: Goodbye{\n\t\t\t\tSources: []uint32{0x01020304},\n\t\t\t\tReason:  \"\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"reason no source\",\n\t\t\tBye: Goodbye{\n\t\t\t\tSources: []uint32{},\n\t\t\t\tReason:  \"foo\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"short reason\",\n\t\t\tBye: Goodbye{\n\t\t\t\tSources: []uint32{},\n\t\t\t\tReason:  \"f\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"count overflow\",\n\t\t\tBye: Goodbye{\n\t\t\t\tSources: tooManySources,\n\t\t\t},\n\t\t\tWantError: errTooManySources,\n\t\t},\n\t\t{\n\t\t\tName: \"reason too long\",\n\t\t\tBye: Goodbye{\n\t\t\t\tSources: []uint32{},\n\t\t\t\tReason:  tooLongText.String(),\n\t\t\t},\n\t\t\tWantError: errReasonTooLong,\n\t\t},\n\t} {\n\t\tdata, err := test.Bye.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar bye Goodbye\n\t\tassert.NoErrorf(t, bye.Unmarshal(data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Bye, bye, \"%q bye round trip mismatch\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "header.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n)\n\n// PacketType specifies the type of an RTCP packet.\ntype PacketType uint8\n\n// RTCP packet types registered with IANA. See:\n//\n//\thttps://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-4\nconst (\n\tTypeSenderReport              PacketType = 200 // RFC 3550, 6.4.1\n\tTypeReceiverReport            PacketType = 201 // RFC 3550, 6.4.2\n\tTypeSourceDescription         PacketType = 202 // RFC 3550, 6.5\n\tTypeGoodbye                   PacketType = 203 // RFC 3550, 6.6\n\tTypeApplicationDefined        PacketType = 204 // RFC 3550, 6.7 (unimplemented)\n\tTypeTransportSpecificFeedback PacketType = 205 // RFC 4585, 6051\n\tTypePayloadSpecificFeedback   PacketType = 206 // RFC 4585, 6.3\n\tTypeExtendedReport            PacketType = 207 // RFC 3611\n\n)\n\n// Transport and Payload specific feedback messages overload the count field to act as a message type.\n// those are listed here.\nconst (\n\tFormatSLI  uint8 = 2\n\tFormatPLI  uint8 = 1\n\tFormatFIR  uint8 = 4\n\tFormatTLN  uint8 = 1\n\tFormatRRR  uint8 = 5\n\tFormatCCFB uint8 = 11\n\tFormatREMB uint8 = 15\n\n\t// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5\n\tFormatTCC uint8 = 15\n)\n\nfunc (p PacketType) String() string {\n\tswitch p {\n\tcase TypeSenderReport:\n\t\treturn \"SR\"\n\tcase TypeReceiverReport:\n\t\treturn \"RR\"\n\tcase TypeSourceDescription:\n\t\treturn \"SDES\"\n\tcase TypeGoodbye:\n\t\treturn \"BYE\"\n\tcase TypeApplicationDefined:\n\t\treturn \"APP\"\n\tcase TypeTransportSpecificFeedback:\n\t\treturn \"TSFB\"\n\tcase TypePayloadSpecificFeedback:\n\t\treturn \"PSFB\"\n\tcase TypeExtendedReport:\n\t\treturn \"XR\"\n\tdefault:\n\t\treturn string(p)\n\t}\n}\n\nconst rtpVersion = 2\n\n// A Header is the common header shared by all RTCP packets.\ntype Header struct {\n\t// If the padding bit is set, this individual RTCP packet contains\n\t// some additional padding octets at the end which are not part of\n\t// the control information but are included in the length field.\n\tPadding bool\n\t// The number of reception reports, sources contained or FMT in this packet (depending on the Type)\n\tCount uint8\n\t// The RTCP packet type for this packet\n\tType PacketType\n\t// The length of this RTCP packet in 32-bit words minus one,\n\t// including the header and any padding.\n\tLength uint16\n}\n\nconst (\n\theaderLength = 4\n\tversionShift = 6\n\tversionMask  = 0x3\n\tpaddingShift = 5\n\tpaddingMask  = 0x1\n\tcountShift   = 0\n\tcountMask    = 0x1f\n\tcountMax     = (1 << 5) - 1\n)\n\n// Marshal encodes the Header in binary.\nfunc (h Header) Marshal() ([]byte, error) {\n\t/*\n\t *  0                   1                   2                   3\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 * |V=2|P|    RC   |   PT=SR=200   |             length            |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\trawPacket := make([]byte, headerLength)\n\n\trawPacket[0] |= rtpVersion << versionShift\n\n\tif h.Padding {\n\t\trawPacket[0] |= 1 << paddingShift\n\t}\n\n\tif h.Count > 31 {\n\t\treturn nil, errInvalidHeader\n\t}\n\trawPacket[0] |= h.Count << countShift //nolint:gosec // rawPacket is created with length headerLength (4)\n\n\trawPacket[1] = uint8(h.Type) //nolint:gosec // rawPacket is created with length headerLength (4)\n\n\tbinary.BigEndian.PutUint16(rawPacket[2:], h.Length)\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the Header from binary.\nfunc (h *Header) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) < headerLength {\n\t\treturn errPacketTooShort\n\t}\n\n\t/*\n\t *  0                   1                   2                   3\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 * |V=2|P|    RC   |      PT       |             length            |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\n\tversion := rawPacket[0] >> versionShift & versionMask\n\tif version != rtpVersion {\n\t\treturn errBadVersion\n\t}\n\n\th.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0\n\th.Count = rawPacket[0] >> countShift & countMask\n\n\th.Type = PacketType(rawPacket[1])\n\n\th.Length = binary.BigEndian.Uint16(rawPacket[2:])\n\n\treturn nil\n}\n"
  },
  {
    "path": "header_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestHeaderUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      Header\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, RR, len=7\n\t\t\t\t0x81, 0xc9, 0x00, 0x07,\n\t\t\t},\n\t\t\tWant: Header{\n\t\t\t\tPadding: false,\n\t\t\t\tCount:   1,\n\t\t\t\tType:    TypeReceiverReport,\n\t\t\t\tLength:  7,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"also valid\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=1, count=1, BYE, len=7\n\t\t\t\t0xa1, 0xcc, 0x00, 0x07,\n\t\t\t},\n\t\t\tWant: Header{\n\t\t\t\tPadding: true,\n\t\t\t\tCount:   1,\n\t\t\t\tType:    TypeApplicationDefined,\n\t\t\t\tLength:  7,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"bad version\",\n\t\t\tData: []byte{\n\t\t\t\t// v=0, p=0, count=0, RR, len=4\n\t\t\t\t0x00, 0xc9, 0x00, 0x04,\n\t\t\t},\n\t\t\tWantError: errBadVersion,\n\t\t},\n\t} {\n\t\tvar h Header\n\t\terr := h.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q header mispmatch\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equalf(t, test.Want, h, \"Unmarshal %q header mismatch\", test.Name)\n\t}\n}\n\nfunc TestHeaderRoundTrip(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tHeader    Header\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tHeader: Header{\n\t\t\t\tPadding: true,\n\t\t\t\tCount:   31,\n\t\t\t\tType:    TypeSenderReport,\n\t\t\t\tLength:  4,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"also valid\",\n\t\t\tHeader: Header{\n\t\t\t\tPadding: false,\n\t\t\t\tCount:   28,\n\t\t\t\tType:    TypeReceiverReport,\n\t\t\t\tLength:  65535,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"invalid count\",\n\t\t\tHeader: Header{\n\t\t\t\tCount: 40,\n\t\t\t},\n\t\t\tWantError: errInvalidHeader,\n\t\t},\n\t} {\n\t\tdata, err := test.Header.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar decoded Header\n\t\tassert.NoErrorf(t, decoded.Unmarshal(data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Header, decoded, \"%q header round trip mismatch\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "packet.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\n// Packet represents an RTCP packet, a protocol used for out-of-band statistics\n// and control information for an RTP session.\ntype Packet interface {\n\t// DestinationSSRC returns an array of SSRC values that this packet refers to.\n\tDestinationSSRC() []uint32\n\n\tMarshal() ([]byte, error)\n\tUnmarshal(rawPacket []byte) error\n\tMarshalSize() int\n}\n\n// Unmarshal takes an entire udp datagram (which may consist of multiple RTCP packets) and\n// returns the unmarshaled packets it contains.\n//\n// If this is a reduced-size RTCP packet a feedback packet (Goodbye, SliceLossIndication, etc)\n// will be returned. Otherwise, the underlying type of the returned packet will be\n// CompoundPacket.\nfunc Unmarshal(rawData []byte) ([]Packet, error) {\n\tvar packets []Packet\n\tfor len(rawData) != 0 {\n\t\tp, processed, err := unmarshal(rawData)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpackets = append(packets, p)\n\t\trawData = rawData[processed:]\n\t}\n\n\tswitch len(packets) {\n\t// Empty packet\n\tcase 0:\n\t\treturn nil, errInvalidHeader\n\t// Multiple Packets\n\tdefault:\n\t\treturn packets, nil\n\t}\n}\n\n// Marshal takes an array of Packets and serializes them to a single buffer.\nfunc Marshal(packets []Packet) ([]byte, error) {\n\tout := make([]byte, 0)\n\tfor _, p := range packets {\n\t\tdata, err := p.Marshal()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tout = append(out, data...)\n\t}\n\n\treturn out, nil\n}\n\n// unmarshal is a factory which pulls the first RTCP packet from a bytestream,\n// and returns it's parsed representation, and the amount of data that was processed.\n//\n//nolint:cyclop\nfunc unmarshal(rawData []byte) (packet Packet, bytesprocessed int, err error) {\n\tvar header Header\n\n\terr = header.Unmarshal(rawData)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tbytesprocessed = int(header.Length+1) * 4\n\tif bytesprocessed > len(rawData) {\n\t\treturn nil, 0, errPacketTooShort\n\t}\n\tinPacket := rawData[:bytesprocessed]\n\n\tswitch header.Type {\n\tcase TypeSenderReport:\n\t\tpacket = new(SenderReport)\n\n\tcase TypeReceiverReport:\n\t\tpacket = new(ReceiverReport)\n\n\tcase TypeSourceDescription:\n\t\tpacket = new(SourceDescription)\n\n\tcase TypeGoodbye:\n\t\tpacket = new(Goodbye)\n\n\tcase TypeTransportSpecificFeedback:\n\t\tswitch header.Count {\n\t\tcase FormatTLN:\n\t\t\tpacket = new(TransportLayerNack)\n\t\tcase FormatRRR:\n\t\t\tpacket = new(RapidResynchronizationRequest)\n\t\tcase FormatTCC:\n\t\t\tpacket = new(TransportLayerCC)\n\t\tcase FormatCCFB:\n\t\t\tpacket = new(CCFeedbackReport)\n\t\tdefault:\n\t\t\tpacket = new(RawPacket)\n\t\t}\n\n\tcase TypePayloadSpecificFeedback:\n\t\tswitch header.Count {\n\t\tcase FormatPLI:\n\t\t\tpacket = new(PictureLossIndication)\n\t\tcase FormatSLI:\n\t\t\tpacket = new(SliceLossIndication)\n\t\tcase FormatREMB:\n\t\t\tpacket = new(ReceiverEstimatedMaximumBitrate)\n\t\tcase FormatFIR:\n\t\t\tpacket = new(FullIntraRequest)\n\t\tdefault:\n\t\t\tpacket = new(RawPacket)\n\t\t}\n\n\tcase TypeExtendedReport:\n\t\tpacket = new(ExtendedReport)\n\n\tcase TypeApplicationDefined:\n\t\tpacket = new(ApplicationDefined)\n\n\tdefault:\n\t\tpacket = new(RawPacket)\n\t}\n\n\terr = packet.Unmarshal(inPacket)\n\n\treturn packet, bytesprocessed, err\n}\n"
  },
  {
    "path": "packet_buffer.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"reflect\"\n\t\"unsafe\"\n)\n\n// These functions implement an introspective structure\n// serializer/deserializer, designed to allow RTCP packet\n// Structs to be self-describing. They currently work with\n// fields of type uint8, uint16, uint32, and uint64 (and\n// types derived from them).\n//\n// - Unexported fields will take up space in the encoded\n//   array, but wil be set to zero when written, and ignore\n//   when read.\n//\n// - Fields that are marked with the tag `encoding:\"omit\"`\n//   will be ignored when reading and writing data.\n//\n// For example:\n//\n//   type Example struct {\n//     A uint32\n//     B bool   `encoding:\"omit\"`\n//     _ uint64\n//     C uint16\n//   }\n//\n// \"A\" will be encoded as four bytes, in network order. \"B\"\n// will not be encoded at all. The anonymous uint64 will\n// encode as 8 bytes of value \"0\", followed by two bytes\n// encoding \"C\" in network order.\n\ntype packetBuffer struct {\n\tbytes []byte\n}\n\nconst omit = \"omit\"\n\n// Writes the structure passed to into the buffer that\n// PacketBuffer is initialized with. This function will\n// modify the PacketBuffer.bytes slice to exclude those\n// bytes that have been written into.\n//\n//nolint:gocognit,cyclop\nfunc (b *packetBuffer) write(v any) error {\n\tvalue := reflect.ValueOf(v)\n\n\t// Indirect is safe to call on non-pointers, and\n\t// will simply return the same value in such cases\n\tvalue = reflect.Indirect(value)\n\n\tswitch value.Kind() {\n\tcase reflect.Uint8:\n\t\tif len(b.bytes) < 1 {\n\t\t\treturn errWrongMarshalSize\n\t\t}\n\t\tif value.CanInterface() {\n\t\t\tb.bytes[0] = byte(value.Uint()) //nolint:gosec //  value.Kind() == reflect.Uint8 guarantees range\n\t\t}\n\t\tb.bytes = b.bytes[1:]\n\tcase reflect.Uint16:\n\t\tif len(b.bytes) < 2 {\n\t\t\treturn errWrongMarshalSize\n\t\t}\n\t\tif value.CanInterface() {\n\t\t\tbinary.BigEndian.PutUint16(b.bytes, uint16(value.Uint())) //nolint:gosec // G115\n\t\t}\n\t\tb.bytes = b.bytes[2:]\n\tcase reflect.Uint32:\n\t\tif len(b.bytes) < 4 {\n\t\t\treturn errWrongMarshalSize\n\t\t}\n\t\tif value.CanInterface() {\n\t\t\tbinary.BigEndian.PutUint32(b.bytes, uint32(value.Uint())) //nolint:gosec // G115\n\t\t}\n\t\tb.bytes = b.bytes[4:]\n\tcase reflect.Uint64:\n\t\tif len(b.bytes) < 8 {\n\t\t\treturn errWrongMarshalSize\n\t\t}\n\t\tif value.CanInterface() {\n\t\t\tbinary.BigEndian.PutUint64(b.bytes, value.Uint())\n\t\t}\n\t\tb.bytes = b.bytes[8:]\n\tcase reflect.Slice:\n\t\tfor i := 0; i < value.Len(); i++ {\n\t\t\tif value.Index(i).CanInterface() {\n\t\t\t\tif err := b.write(value.Index(i).Interface()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tb.bytes = b.bytes[value.Index(i).Type().Size():]\n\t\t\t}\n\t\t}\n\tcase reflect.Struct:\n\t\tfor i := 0; i < value.NumField(); i++ {\n\t\t\tencoding := value.Type().Field(i).Tag.Get(\"encoding\")\n\t\t\tif encoding == omit {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif value.Field(i).CanInterface() {\n\t\t\t\tif err := b.write(value.Field(i).Interface()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tadvance := int(value.Field(i).Type().Size()) //nolint:gosec // RTCP struct field sizes are small and controlled\n\t\t\t\tif len(b.bytes) < advance {\n\t\t\t\t\treturn errWrongMarshalSize\n\t\t\t\t}\n\t\t\t\tb.bytes = b.bytes[advance:]\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn errBadStructMemberType\n\t}\n\n\treturn nil\n}\n\n// Reads bytes from the buffer as necessary to populate\n// the structure passed as a parameter. This function will\n// modify the PacketBuffer.bytes slice to exclude those\n// bytes that have already been read.\n//\n//nolint:gocognit,cyclop\nfunc (b *packetBuffer) read(v any) error {\n\tptr := reflect.ValueOf(v)\n\tif ptr.Kind() != reflect.Ptr {\n\t\treturn errBadReadParameter\n\t}\n\tvalue := reflect.Indirect(ptr)\n\n\t// If this is an interface, we need to make it concrete before using it\n\tif value.Kind() == reflect.Interface {\n\t\tvalue = reflect.ValueOf(value.Interface())\n\t}\n\tvalue = reflect.Indirect(value)\n\n\tswitch value.Kind() {\n\tcase reflect.Uint8:\n\t\tif len(b.bytes) < 1 {\n\t\t\treturn errWrongMarshalSize\n\t\t}\n\t\tvalue.SetUint(uint64(b.bytes[0]))\n\t\tb.bytes = b.bytes[1:]\n\n\tcase reflect.Uint16:\n\t\tif len(b.bytes) < 2 {\n\t\t\treturn errWrongMarshalSize\n\t\t}\n\t\tvalue.SetUint(uint64(binary.BigEndian.Uint16(b.bytes)))\n\t\tb.bytes = b.bytes[2:]\n\n\tcase reflect.Uint32:\n\t\tif len(b.bytes) < 4 {\n\t\t\treturn errWrongMarshalSize\n\t\t}\n\t\tvalue.SetUint(uint64(binary.BigEndian.Uint32(b.bytes)))\n\t\tb.bytes = b.bytes[4:]\n\n\tcase reflect.Uint64:\n\t\tif len(b.bytes) < 8 {\n\t\t\treturn errWrongMarshalSize\n\t\t}\n\t\tvalue.SetUint(binary.BigEndian.Uint64(b.bytes))\n\t\tb.bytes = b.bytes[8:]\n\n\tcase reflect.Slice:\n\t\t// If we encounter a slice, we consume the rest of the data\n\t\t// in the buffer and load it into the slice.\n\t\tfor len(b.bytes) > 0 {\n\t\t\tnewElementPtr := reflect.New(value.Type().Elem())\n\t\t\tif err := b.read(newElementPtr.Interface()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif value.CanSet() {\n\t\t\t\tvalue.Set(reflect.Append(value, reflect.Indirect(newElementPtr)))\n\t\t\t}\n\t\t}\n\n\tcase reflect.Struct:\n\t\tfor i := 0; i < value.NumField(); i++ {\n\t\t\tencoding := value.Type().Field(i).Tag.Get(\"encoding\")\n\t\t\tif encoding == omit {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif value.Field(i).CanInterface() {\n\t\t\t\tfield := value.Field(i)\n\t\t\t\tnewFieldPtr := reflect.NewAt(\n\t\t\t\t\t//nolint:gosec // This is the only way to get a typed pointer to a structure's field\n\t\t\t\t\tfield.Type(), unsafe.Pointer(field.UnsafeAddr()),\n\t\t\t\t)\n\t\t\t\tif err := b.read(newFieldPtr.Interface()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tadvance := int(value.Field(i).Type().Size()) //nolint:gosec //Size comes from type system and is bounded\n\t\t\t\tif len(b.bytes) < advance {\n\t\t\t\t\treturn errWrongMarshalSize\n\t\t\t\t}\n\t\t\t\tb.bytes = b.bytes[advance:]\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\treturn errBadStructMemberType\n\t}\n\n\treturn nil\n}\n\n// Consumes `size` bytes and returns them as an\n// independent PacketBuffer.\nfunc (b *packetBuffer) split(size int) packetBuffer {\n\tif size > len(b.bytes) {\n\t\tsize = len(b.bytes)\n\t}\n\tnewBuffer := packetBuffer{bytes: b.bytes[:size]}\n\n\tb.bytes = b.bytes[size:]\n\n\treturn newBuffer\n}\n\n// Returns the size that a structure will encode into.\n// This fuction doesn't check that Write() will succeed,\n// and may return unexpectedly large results for those\n// structures that Write() will fail on.\nfunc wireSize(v any) int {\n\tvalue := reflect.ValueOf(v)\n\t// Indirect is safe to call on non-pointers, and\n\t// will simply return the same value in such cases\n\tvalue = reflect.Indirect(value)\n\tsize := int(0)\n\n\tswitch value.Kind() {\n\tcase reflect.Slice:\n\t\tfor i := 0; i < value.Len(); i++ {\n\t\t\tif value.Index(i).CanInterface() {\n\t\t\t\tsize += wireSize(value.Index(i).Interface())\n\t\t\t} else {\n\t\t\t\tsize += int(value.Index(i).Type().Size()) //nolint:gosec //  RTCP element sizes are small and bounded\n\t\t\t}\n\t\t}\n\n\tcase reflect.Struct:\n\t\tfor i := 0; i < value.NumField(); i++ {\n\t\t\tencoding := value.Type().Field(i).Tag.Get(\"encoding\")\n\t\t\tif encoding == omit {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif value.Field(i).CanInterface() {\n\t\t\t\tsize += wireSize(value.Field(i).Interface())\n\t\t\t} else {\n\t\t\t\tsize += int(value.Field(i).Type().Size()) // nolint:gosec // Size comes from type system and is bounded\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\tsize = int(value.Type().Size()) // nolint:gosec // Size comes from type system and is bounde\n\t}\n\n\treturn size\n}\n"
  },
  {
    "path": "packet_buffer_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestWrite(t *testing.T) {\n\ttype Subtree struct {\n\t\tSubA uint32\n\t\tSubB []uint8\n\t}\n\n\tstructure := struct {\n\t\tA uint8\n\t\tZ uint32 `encoding:\"omit\"`\n\t\tB uint16\n\t\tC uint32\n\t\tD uint64\n\t\t_ uint8\n\t\tE []uint16\n\t\tF Subtree\n\t\tG []Subtree\n\t}{\n\t\t0xf8,\n\t\t0x01234567,\n\t\t0x1234,\n\t\t0x56789ABC,\n\t\t0x0102030405060708,\n\t\t0x12,\n\t\t[]uint16{0x0E, 0x02FF},\n\t\tSubtree{0x11223344, []uint8{9, 8, 7, 6, 5, 4, 3, 2, 1}},\n\t\t[]Subtree{{0x01, []uint8{1, 2, 3, 4}}, {0x02, []uint8{5, 6, 7, 8}}},\n\t}\n\texpected := []byte{\n\t\t0xf8,\n\t\t0x12, 0x34,\n\t\t0x56, 0x78, 0x9A, 0xBC,\n\t\t0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,\n\t\t0x00,\n\t\t0x00, 0x0E, 0x02, 0xFF,\n\t\t0x11, 0x22, 0x33, 0x44, 9, 8, 7, 6, 5, 4, 3, 2, 1,\n\t\t0x00, 0x00, 0x00, 0x01, 1, 2, 3, 4, 0x00, 0x00, 0x00, 0x02, 5, 6, 7, 8,\n\t}\n\tassert.Equal(t, len(expected), wireSize(structure))\n\n\traw := make([]byte, len(expected))\n\tbuffer := packetBuffer{bytes: raw}\n\terr := buffer.write(structure)\n\tassert.NoError(t, err)\n\tassert.Equal(t, expected, raw)\n\n\t// Check for overflow\n\traw = make([]byte, len(expected)-1)\n\tbuffer = packetBuffer{bytes: raw}\n\terr = buffer.write(structure)\n\tassert.ErrorIs(t, err, errWrongMarshalSize)\n}\n\nfunc TestReadUint8(t *testing.T) {\n\tconst expected = 0x01\n\traw := []byte{expected}\n\toutput := uint8(0)\n\tbuffer := packetBuffer{bytes: raw}\n\terr := buffer.read(&output)\n\tassert.NoError(t, err)\n\tassert.Equal(t, uint8(expected), output)\n}\n\nfunc TestReadUint16(t *testing.T) {\n\tconst expected = 0x0102\n\traw := []byte{0x01, 0x02}\n\toutput := uint16(0)\n\tbuffer := packetBuffer{bytes: raw}\n\terr := buffer.read(&output)\n\tassert.NoError(t, err)\n\tassert.Equal(t, uint16(expected), output)\n}\n\nfunc TestReadUint32(t *testing.T) {\n\tconst expected = 0x01020304\n\traw := []byte{0x01, 0x02, 0x03, 0x04}\n\toutput := uint32(0)\n\tbuffer := packetBuffer{bytes: raw}\n\terr := buffer.read(&output)\n\tassert.NoError(t, err)\n\tassert.Equal(t, uint32(expected), output)\n}\n\nfunc TestReadUint64(t *testing.T) {\n\texpected := uint64(0x0102030405060708)\n\traw := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}\n\toutput := uint64(0)\n\tbuffer := packetBuffer{bytes: raw}\n\terr := buffer.read(&output)\n\tassert.NoError(t, err)\n\tassert.Equal(t, expected, output)\n}\n\nfunc TestReadStruct(t *testing.T) {\n\ttype S struct {\n\t\tA uint8\n\t\tB uint16\n\t\tC uint32\n\t\tD uint64\n\t}\n\texpected := S{0x01, 0x0203, 0x04050607, 0x08090A0B0C0D0E0F}\n\traw := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}\n\tvar output S\n\tbuffer := packetBuffer{bytes: raw}\n\terr := buffer.read(&output)\n\tassert.NoError(t, err)\n\tassert.Equal(t, expected, output)\n}\n\nfunc TestReadSlice(t *testing.T) {\n\texpected := []uint16{0x0102, 0x0304, 0x0506, 0x0708}\n\traw := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}\n\tvar output []uint16\n\tbuffer := packetBuffer{bytes: raw}\n\terr := buffer.read(&output)\n\tassert.NoError(t, err)\n\tassert.Equal(t, expected, output)\n}\n\nfunc TestReadComplex(t *testing.T) {\n\traw := []byte{\n\t\t0xf8,\n\t\t0x12, 0x34,\n\t\t0x56, 0x78, 0x9A, 0xBC,\n\t\t0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,\n\t\t0x12,\n\t\t0x11, 0x22, 0x33, 0x44, 9,\n\t\t0x00, 0x00, 0x00, 0x01, 1, 0x00, 0x00, 0x00, 0x02, 5,\n\t}\n\n\ttype Subtree struct {\n\t\tSubA uint32\n\t\tSubB uint8\n\t}\n\n\ttype Tree struct {\n\t\tA uint8\n\t\tB uint16\n\t\tC uint32\n\t\tD uint64\n\t\t_ uint8\n\t\tF Subtree\n\t\tG []Subtree\n\t}\n\n\texpected := Tree{\n\t\t0xf8,\n\t\t0x1234,\n\t\t0x56789ABC,\n\t\t0x0102030405060708,\n\t\t0x00,\n\t\tSubtree{0x11223344, 9},\n\t\t[]Subtree{{0x01, 1}, {0x02, 5}},\n\t}\n\n\tvar output Tree\n\n\tbuffer := packetBuffer{bytes: raw}\n\terr := buffer.read(&output)\n\tassert.NoError(t, err)\n\tassert.Equal(t, expected, output)\n}\n"
  },
  {
    "path": "packet_stringifier.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n)\n\n/*\nConverts an RTCP Packet into a human-readable format. The Packets\nthemselves can control the presentation as follows:\n\n  - Fields of a type that have a String() method will be formatted\n    with that String method (which should not emit '\\n' characters)\n\n  - Otherwise, fields with a tag containing a \"fmt\" string will use that\n    format when serializing the value. For example, to format an SSRC\n    value as base 16 insted of base 10:\n\n    type ExamplePacket struct {\n    LocalSSRC   uint32   `fmt:\"0x%X\"`\n    RemotsSSRCs []uint32 `fmt:\"%X\"`\n    }\n\n- If no fmt string is present, \"%+v\" is used by default\n\nThe intention of this stringify() function is to simplify creation\nof String() methods on new packet types, as it provides a simple\nbaseline implementation that works well in the majority of cases.\n*/\nfunc stringify(p Packet) string {\n\tvalue := reflect.Indirect(reflect.ValueOf(p))\n\n\treturn formatField(value.Type().String(), \"\", p, \"\")\n}\n\n//nolint:gocognit,cyclop\nfunc formatField(name string, format string, f any, indent string) string {\n\tout := indent\n\tvalue := reflect.ValueOf(f)\n\n\tif !value.IsValid() {\n\t\treturn fmt.Sprintf(\"%s%s: <nil>\\n\", out, name)\n\t}\n\n\tisPacket := reflect.TypeOf(f).Implements(reflect.TypeFor[Packet]())\n\n\t// Resolve pointers to their underlying values\n\tif value.Type().Kind() == reflect.Ptr && !value.IsNil() {\n\t\tunderlying := reflect.Indirect(value)\n\t\tif underlying.IsValid() {\n\t\t\tvalue = underlying\n\t\t}\n\t}\n\n\t// If the field type has a custom String method, use that\n\t// (unless we're a packet, since we want to avoid recursing\n\t// back into this function if the Packet's String() method\n\t// uses it)\n\tif stringMethod := value.MethodByName(\"String\"); !isPacket && stringMethod.IsValid() {\n\t\tout += fmt.Sprintf(\"%s: %s\\n\", name, stringMethod.Call([]reflect.Value{}))\n\n\t\treturn out\n\t}\n\n\tswitch value.Kind() {\n\tcase reflect.Struct:\n\t\tout += fmt.Sprintf(\"%s:\\n\", name)\n\t\tfor i := 0; i < value.NumField(); i++ {\n\t\t\tif value.Field(i).CanInterface() {\n\t\t\t\tformat = value.Type().Field(i).Tag.Get(\"fmt\")\n\t\t\t\tif format == \"\" {\n\t\t\t\t\tformat = \"%+v\"\n\t\t\t\t}\n\t\t\t\tout += formatField(value.Type().Field(i).Name, format, value.Field(i).Interface(), indent+\"\\t\")\n\t\t\t}\n\t\t}\n\tcase reflect.Slice:\n\t\tchildKind := value.Type().Elem().Kind()\n\t\t_, hasStringMethod := value.Type().Elem().MethodByName(\"String\")\n\t\tif hasStringMethod || childKind == reflect.Struct || childKind == reflect.Ptr ||\n\t\t\tchildKind == reflect.Interface || childKind == reflect.Slice {\n\t\t\tout += fmt.Sprintf(\"%s:\\n\", name)\n\t\t\tfor i := 0; i < value.Len(); i++ {\n\t\t\t\tchildName := fmt.Sprint(i)\n\t\t\t\t// Since interfaces can hold different types of things, we add the\n\t\t\t\t// most specific type name to the name to make it clear what the\n\t\t\t\t// subsequent fields represent.\n\t\t\t\tif value.Index(i).Kind() == reflect.Interface {\n\t\t\t\t\tchildName += fmt.Sprintf(\" (%s)\", reflect.Indirect(reflect.ValueOf(value.Index(i).Interface())).Type())\n\t\t\t\t}\n\t\t\t\tif value.Index(i).CanInterface() {\n\t\t\t\t\tout += formatField(childName, format, value.Index(i).Interface(), indent+\"\\t\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn out\n\t\t}\n\n\t\t// If we didn't take care of stringing the value already, we fall through to the\n\t\t// generic case. This will print slices of basic types on a single line.\n\t\tfallthrough\n\tdefault:\n\t\tif value.CanInterface() {\n\t\t\tout += fmt.Sprintf(\"%s: \"+format+\"\\n\", name, value.Interface())\n\t\t}\n\t}\n\n\treturn out\n}\n"
  },
  {
    "path": "packet_stringifier_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n//nolint:maintidx\nfunc TestPrint(t *testing.T) {\n\ttype Tests struct {\n\t\tpacket   Packet\n\t\texpected string\n\t}\n\n\ttests := []Tests{\n\t\t{\n\t\t\t&ExtendedReport{\n\t\t\t\tSenderSSRC: 0x01020304,\n\t\t\t\tReports: []ReportBlock{\n\t\t\t\t\t&LossRLEReportBlock{\n\t\t\t\t\t\tXRHeader: XRHeader{\n\t\t\t\t\t\t\tBlockType: LossRLEReportBlockType,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSSRC:     0x12345689,\n\t\t\t\t\t\tBeginSeq: 5,\n\t\t\t\t\t\tEndSeq:   12,\n\t\t\t\t\t\tChunks: []Chunk{\n\t\t\t\t\t\t\tChunk(0x4006),\n\t\t\t\t\t\t\tChunk(0x0006),\n\t\t\t\t\t\t\tChunk(0x8765),\n\t\t\t\t\t\t\tChunk(0x0000),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t&DuplicateRLEReportBlock{\n\t\t\t\t\t\tXRHeader: XRHeader{\n\t\t\t\t\t\t\tBlockType: DuplicateRLEReportBlockType,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSSRC:     0x12345689,\n\t\t\t\t\t\tBeginSeq: 5,\n\t\t\t\t\t\tEndSeq:   12,\n\t\t\t\t\t\tChunks: []Chunk{\n\t\t\t\t\t\t\tChunk(0x4123),\n\t\t\t\t\t\t\tChunk(0x3FFF),\n\t\t\t\t\t\t\tChunk(0xFFFF),\n\t\t\t\t\t\t\tChunk(0x0000),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t&PacketReceiptTimesReportBlock{\n\t\t\t\t\t\tXRHeader: XRHeader{\n\t\t\t\t\t\t\tBlockType: PacketReceiptTimesReportBlockType,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSSRC:     0x98765432,\n\t\t\t\t\t\tBeginSeq: 15432,\n\t\t\t\t\t\tEndSeq:   15577,\n\t\t\t\t\t\tReceiptTime: []uint32{\n\t\t\t\t\t\t\t0x11111111,\n\t\t\t\t\t\t\t0x22222222,\n\t\t\t\t\t\t\t0x33333333,\n\t\t\t\t\t\t\t0x44444444,\n\t\t\t\t\t\t\t0x55555555,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t&ReceiverReferenceTimeReportBlock{\n\t\t\t\t\t\tXRHeader: XRHeader{\n\t\t\t\t\t\t\tBlockType: ReceiverReferenceTimeReportBlockType,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tNTPTimestamp: 0x0102030405060708,\n\t\t\t\t\t},\n\t\t\t\t\t&DLRRReportBlock{\n\t\t\t\t\t\tXRHeader: XRHeader{\n\t\t\t\t\t\t\tBlockType: DLRRReportBlockType,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tReports: []DLRRReport{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tSSRC:   0x88888888,\n\t\t\t\t\t\t\t\tLastRR: 0x12345678,\n\t\t\t\t\t\t\t\tDLRR:   0x99999999,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tSSRC:   0x09090909,\n\t\t\t\t\t\t\t\tLastRR: 0x12345678,\n\t\t\t\t\t\t\t\tDLRR:   0x99999999,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tSSRC:   0x11223344,\n\t\t\t\t\t\t\t\tLastRR: 0x12345678,\n\t\t\t\t\t\t\t\tDLRR:   0x99999999,\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&StatisticsSummaryReportBlock{\n\t\t\t\t\t\tXRHeader{\n\t\t\t\t\t\t\tBlockType: StatisticsSummaryReportBlockType,\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttrue, true, true, ToHIPv4,\n\t\t\t\t\t\t0xFEDCBA98,\n\t\t\t\t\t\t0x1234, 0x5678,\n\t\t\t\t\t\t0x11111111,\n\t\t\t\t\t\t0x22222222,\n\t\t\t\t\t\t0x33333333,\n\t\t\t\t\t\t0x44444444,\n\t\t\t\t\t\t0x55555555,\n\t\t\t\t\t\t0x66666666,\n\t\t\t\t\t\t0x01, 0x02, 0x03, 0x04,\n\t\t\t\t\t},\n\t\t\t\t\t&VoIPMetricsReportBlock{\n\t\t\t\t\t\tXRHeader{\n\t\t\t\t\t\t\tBlockType: VoIPMetricsReportBlockType,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t0x89ABCDEF,\n\t\t\t\t\t\t0x05, 0x06, 0x07, 0x08,\n\t\t\t\t\t\t0x1111, 0x2222, 0x3333, 0x4444,\n\t\t\t\t\t\t0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,\n\t\t\t\t\t\t0x00,\n\t\t\t\t\t\t0x1122, 0x3344, 0x5566,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// nolint\n\t\t\t\"rtcp.ExtendedReport:\\n\" +\n\t\t\t\t\"\\tSenderSSRC: 0x1020304\\n\" +\n\t\t\t\t\"\\tReports:\\n\" +\n\t\t\t\t\"\\t\\t0 (rtcp.LossRLEReportBlock):\\n\" +\n\t\t\t\t\"\\t\\t\\tXRHeader:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockType: [LossRLEReportBlockType]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tTypeSpecific: 0x0\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockLength: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tT: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tSSRC: 0x12345689\\n\" +\n\t\t\t\t\"\\t\\t\\tBeginSeq: 5\\n\" +\n\t\t\t\t\"\\t\\t\\tEndSeq: 12\\n\" +\n\t\t\t\t\"\\t\\t\\tChunks:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t0: [[RunLength type=1, length=6]]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t1: [[RunLength type=0, length=6]]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t2: [[BitVector 0b000011101100101]]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t3: [[TerminatingNull]]\\n\" +\n\t\t\t\t\"\\t\\t1 (rtcp.DuplicateRLEReportBlock):\\n\" +\n\t\t\t\t\"\\t\\t\\tXRHeader:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockType: [DuplicateRLEReportBlockType]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tTypeSpecific: 0x0\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockLength: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tT: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tSSRC: 0x12345689\\n\" +\n\t\t\t\t\"\\t\\t\\tBeginSeq: 5\\n\" +\n\t\t\t\t\"\\t\\t\\tEndSeq: 12\\n\" +\n\t\t\t\t\"\\t\\t\\tChunks:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t0: [[RunLength type=1, length=291]]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t1: [[RunLength type=0, length=16383]]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t2: [[BitVector 0b111111111111111]]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t3: [[TerminatingNull]]\\n\" +\n\t\t\t\t\"\\t\\t2 (rtcp.PacketReceiptTimesReportBlock):\\n\" +\n\t\t\t\t\"\\t\\t\\tXRHeader:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockType: [PacketReceiptTimesReportBlockType]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tTypeSpecific: 0x0\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockLength: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tT: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tSSRC: 0x98765432\\n\" +\n\t\t\t\t\"\\t\\t\\tBeginSeq: 15432\\n\" +\n\t\t\t\t\"\\t\\t\\tEndSeq: 15577\\n\" +\n\t\t\t\t\"\\t\\t\\tReceiptTime: [286331153 572662306 858993459 1145324612 1431655765]\\n\" +\n\t\t\t\t\"\\t\\t3 (rtcp.ReceiverReferenceTimeReportBlock):\\n\" +\n\t\t\t\t\"\\t\\t\\tXRHeader:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockType: [ReceiverReferenceTimeReportBlockType]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tTypeSpecific: 0x0\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockLength: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tNTPTimestamp: 72623859790382856\\n\" +\n\t\t\t\t\"\\t\\t4 (rtcp.DLRRReportBlock):\\n\" +\n\t\t\t\t\"\\t\\t\\tXRHeader:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockType: [DLRRReportBlockType]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tTypeSpecific: 0x0\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockLength: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tReports:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tSSRC: 0x88888888\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tLastRR: 305419896\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tDLRR: 2576980377\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t1:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tSSRC: 0x9090909\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tLastRR: 305419896\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tDLRR: 2576980377\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t2:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tSSRC: 0x11223344\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tLastRR: 305419896\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tDLRR: 2576980377\\n\" +\n\t\t\t\t\"\\t\\t5 (rtcp.StatisticsSummaryReportBlock):\\n\" +\n\t\t\t\t\"\\t\\t\\tXRHeader:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockType: [StatisticsSummaryReportBlockType]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tTypeSpecific: 0x0\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockLength: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tLossReports: true\\n\" +\n\t\t\t\t\"\\t\\t\\tDuplicateReports: true\\n\" +\n\t\t\t\t\"\\t\\t\\tJitterReports: true\\n\" +\n\t\t\t\t\"\\t\\t\\tTTLorHopLimit: [[ToH = IPv4]]\\n\" +\n\t\t\t\t\"\\t\\t\\tSSRC: 0xFEDCBA98\\n\" +\n\t\t\t\t\"\\t\\t\\tBeginSeq: 4660\\n\" +\n\t\t\t\t\"\\t\\t\\tEndSeq: 22136\\n\" +\n\t\t\t\t\"\\t\\t\\tLostPackets: 286331153\\n\" +\n\t\t\t\t\"\\t\\t\\tDupPackets: 572662306\\n\" +\n\t\t\t\t\"\\t\\t\\tMinJitter: 858993459\\n\" +\n\t\t\t\t\"\\t\\t\\tMaxJitter: 1145324612\\n\" +\n\t\t\t\t\"\\t\\t\\tMeanJitter: 1431655765\\n\" +\n\t\t\t\t\"\\t\\t\\tDevJitter: 1717986918\\n\" +\n\t\t\t\t\"\\t\\t\\tMinTTLOrHL: 1\\n\" +\n\t\t\t\t\"\\t\\t\\tMaxTTLOrHL: 2\\n\" +\n\t\t\t\t\"\\t\\t\\tMeanTTLOrHL: 3\\n\" +\n\t\t\t\t\"\\t\\t\\tDevTTLOrHL: 4\\n\" +\n\t\t\t\t\"\\t\\t6 (rtcp.VoIPMetricsReportBlock):\\n\" +\n\t\t\t\t\"\\t\\t\\tXRHeader:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockType: [VoIPMetricsReportBlockType]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tTypeSpecific: 0x0\\n\" +\n\t\t\t\t\"\\t\\t\\t\\tBlockLength: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tSSRC: 0x89ABCDEF\\n\" +\n\t\t\t\t\"\\t\\t\\tLossRate: 5\\n\" +\n\t\t\t\t\"\\t\\t\\tDiscardRate: 6\\n\" +\n\t\t\t\t\"\\t\\t\\tBurstDensity: 7\\n\" +\n\t\t\t\t\"\\t\\t\\tGapDensity: 8\\n\" +\n\t\t\t\t\"\\t\\t\\tBurstDuration: 4369\\n\" +\n\t\t\t\t\"\\t\\t\\tGapDuration: 8738\\n\" +\n\t\t\t\t\"\\t\\t\\tRoundTripDelay: 13107\\n\" +\n\t\t\t\t\"\\t\\t\\tEndSystemDelay: 17476\\n\" +\n\t\t\t\t\"\\t\\t\\tSignalLevel: 17\\n\" +\n\t\t\t\t\"\\t\\t\\tNoiseLevel: 34\\n\" +\n\t\t\t\t\"\\t\\t\\tRERL: 51\\n\" +\n\t\t\t\t\"\\t\\t\\tGmin: 68\\n\" +\n\t\t\t\t\"\\t\\t\\tRFactor: 85\\n\" +\n\t\t\t\t\"\\t\\t\\tExtRFactor: 102\\n\" +\n\t\t\t\t\"\\t\\t\\tMOSLQ: 119\\n\" +\n\t\t\t\t\"\\t\\t\\tMOSCQ: 136\\n\" +\n\t\t\t\t\"\\t\\t\\tRXConfig: 153\\n\" +\n\t\t\t\t\"\\t\\t\\tJBNominal: 4386\\n\" +\n\t\t\t\t\"\\t\\t\\tJBMaximum: 13124\\n\" +\n\t\t\t\t\"\\t\\t\\tJBAbsMax: 21862\\n\",\n\t\t},\n\t\t{\n\t\t\t&FullIntraRequest{\n\t\t\t\tSenderSSRC: 0x0,\n\t\t\t\tMediaSSRC:  0x4bc4fcb4,\n\t\t\t\tFIR: []FIREntry{\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:           0x12345678,\n\t\t\t\t\t\tSequenceNumber: 0x42,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:           0x98765432,\n\t\t\t\t\t\tSequenceNumber: 0x57,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// nolint\n\t\t\t\"rtcp.FullIntraRequest:\\n\" +\n\t\t\t\t\"\\tSenderSSRC: 0\\n\" +\n\t\t\t\t\"\\tMediaSSRC: 1271200948\\n\" +\n\t\t\t\t\"\\tFIR:\\n\" +\n\t\t\t\t\"\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\tSSRC: 305419896\\n\" +\n\t\t\t\t\"\\t\\t\\tSequenceNumber: 66\\n\" +\n\t\t\t\t\"\\t\\t1:\\n\" +\n\t\t\t\t\"\\t\\t\\tSSRC: 2557891634\\n\" +\n\t\t\t\t\"\\t\\t\\tSequenceNumber: 87\\n\",\n\t\t},\n\t\t{\n\t\t\t&Goodbye{\n\t\t\t\tSources: []uint32{\n\t\t\t\t\t0x01020304,\n\t\t\t\t\t0x05060708,\n\t\t\t\t},\n\t\t\t\tReason: \"because\",\n\t\t\t},\n\t\t\t\"rtcp.Goodbye:\\n\" +\n\t\t\t\t\"\\tSources: [16909060 84281096]\\n\" +\n\t\t\t\t\"\\tReason: because\\n\",\n\t\t},\n\t\t{\n\t\t\t&ReceiverReport{\n\t\t\t\tSSRC: 0x902f9e2e,\n\t\t\t\tReports: []ReceptionReport{{\n\t\t\t\t\tSSRC:               0xbc5e9a40,\n\t\t\t\t\tFractionLost:       0,\n\t\t\t\t\tTotalLost:          0,\n\t\t\t\t\tLastSequenceNumber: 0x46e1,\n\t\t\t\t\tJitter:             273,\n\t\t\t\t\tLastSenderReport:   0x9f36432,\n\t\t\t\t\tDelay:              150137,\n\t\t\t\t}},\n\t\t\t\tProfileExtensions: []byte{},\n\t\t\t},\n\t\t\t\"rtcp.ReceiverReport:\\n\" +\n\t\t\t\t\"\\tSSRC: 2419039790\\n\" +\n\t\t\t\t\"\\tReports:\\n\" +\n\t\t\t\t\"\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\tSSRC: 3160316480\\n\" +\n\t\t\t\t\"\\t\\t\\tFractionLost: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tTotalLost: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tLastSequenceNumber: 18145\\n\" +\n\t\t\t\t\"\\t\\t\\tJitter: 273\\n\" +\n\t\t\t\t\"\\t\\t\\tLastSenderReport: 166945842\\n\" +\n\t\t\t\t\"\\t\\t\\tDelay: 150137\\n\" +\n\t\t\t\t\"\\tProfileExtensions: []\\n\",\n\t\t},\n\t\t{\n\t\t\tNewCNAMESourceDescription(0x902f9e2e, \"{9c00eb92-1afb-9d49-a47d-91f64eee69f5}\"),\n\t\t\t\"rtcp.SourceDescription:\\n\" +\n\t\t\t\t\"\\tChunks:\\n\" +\n\t\t\t\t\"\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\tSource: 2419039790\\n\" +\n\t\t\t\t\"\\t\\t\\tItems:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tType: [CNAME]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tText: {9c00eb92-1afb-9d49-a47d-91f64eee69f5}\\n\",\n\t\t},\n\t\t{\n\t\t\t&PictureLossIndication{\n\t\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t\t},\n\t\t\t// nolint\n\t\t\t\"rtcp.PictureLossIndication:\\n\" +\n\t\t\t\t\"\\tSenderSSRC: 2419039790\\n\" +\n\t\t\t\t\"\\tMediaSSRC: 2419039790\\n\",\n\t\t},\n\t\t{\n\t\t\t&RapidResynchronizationRequest{\n\t\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t\t},\n\t\t\t\"rtcp.RapidResynchronizationRequest:\\n\" +\n\t\t\t\t\"\\tSenderSSRC: 2419039790\\n\" +\n\t\t\t\t\"\\tMediaSSRC: 2419039790\\n\",\n\t\t},\n\t\t{\n\t\t\t&ReceiverEstimatedMaximumBitrate{\n\t\t\t\tSenderSSRC: 1,\n\t\t\t\tBitrate:    8927168,\n\t\t\t\tSSRCs:      []uint32{1215622422},\n\t\t\t},\n\t\t\t\"rtcp.ReceiverEstimatedMaximumBitrate:\\n\" +\n\t\t\t\t\"\\tSenderSSRC: 1\\n\" +\n\t\t\t\t\"\\tBitrate: 8.927168e+06\\n\" +\n\t\t\t\t\"\\tSSRCs: [1215622422]\\n\",\n\t\t},\n\t\t{\n\t\t\t&SenderReport{\n\t\t\t\tSSRC:        0x902f9e2e,\n\t\t\t\tNTPTime:     0xda8bd1fcdddda05a,\n\t\t\t\tRTPTime:     0xaaf4edd5,\n\t\t\t\tPacketCount: 1,\n\t\t\t\tOctetCount:  2,\n\t\t\t\tReports: []ReceptionReport{{\n\t\t\t\t\tSSRC:               0xbc5e9a40,\n\t\t\t\t\tFractionLost:       0,\n\t\t\t\t\tTotalLost:          0,\n\t\t\t\t\tLastSequenceNumber: 0x46e1,\n\t\t\t\t\tJitter:             273,\n\t\t\t\t\tLastSenderReport:   0x9f36432,\n\t\t\t\t\tDelay:              150137,\n\t\t\t\t}},\n\t\t\t\tProfileExtensions: []byte{\n\t\t\t\t\t0x81, 0xca, 0x0, 0x6,\n\t\t\t\t\t0x2b, 0x7e, 0xc0, 0xc5,\n\t\t\t\t\t0x1, 0x10, 0x4c, 0x63,\n\t\t\t\t\t0x49, 0x66, 0x7a, 0x58,\n\t\t\t\t\t0x6f, 0x6e, 0x44, 0x6f,\n\t\t\t\t\t0x72, 0x64, 0x53, 0x65,\n\t\t\t\t\t0x57, 0x36, 0x0, 0x0,\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"rtcp.SenderReport:\\n\" +\n\t\t\t\t\"\\tSSRC: 2419039790\\n\" +\n\t\t\t\t\"\\tNTPTime: 15747911406015324250\\n\" +\n\t\t\t\t\"\\tRTPTime: 2868178389\\n\" +\n\t\t\t\t\"\\tPacketCount: 1\\n\" +\n\t\t\t\t\"\\tOctetCount: 2\\n\" +\n\t\t\t\t\"\\tReports:\\n\" +\n\t\t\t\t\"\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\tSSRC: 3160316480\\n\" +\n\t\t\t\t\"\\t\\t\\tFractionLost: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tTotalLost: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tLastSequenceNumber: 18145\\n\" +\n\t\t\t\t\"\\t\\t\\tJitter: 273\\n\" +\n\t\t\t\t\"\\t\\t\\tLastSenderReport: 166945842\\n\" +\n\t\t\t\t\"\\t\\t\\tDelay: 150137\\n\" +\n\t\t\t\t\"\\tProfileExtensions: \" +\n\t\t\t\t\"[129 202 0 6 43 126 192 197 1 16 76 99 73 102 122 88 111 110 68 111 114 100 83 101 87 54 0 0]\\n\",\n\t\t},\n\t\t{\n\t\t\t&SliceLossIndication{\n\t\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t\t\tSLI:        []SLIEntry{{0xaaa, 0, 0x2C}},\n\t\t\t},\n\t\t\t\"rtcp.SliceLossIndication:\\n\" +\n\t\t\t\t\"\\tSenderSSRC: 2419039790\\n\" +\n\t\t\t\t\"\\tMediaSSRC: 2419039790\\n\" +\n\t\t\t\t\"\\tSLI:\\n\" +\n\t\t\t\t\"\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\tFirst: 2730\\n\" +\n\t\t\t\t\"\\t\\t\\tNumber: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tPicture: 44\\n\",\n\t\t},\n\t\t{\n\t\t\t&SourceDescription{\n\t\t\t\tChunks: []SourceDescriptionChunk{\n\t\t\t\t\t{\n\t\t\t\t\t\tSource: 0x10000000,\n\t\t\t\t\t\tItems: []SourceDescriptionItem{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType: SDESCNAME,\n\t\t\t\t\t\t\t\tText: \"A\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType: SDESPhone,\n\t\t\t\t\t\t\t\tText: \"B\",\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\t\"rtcp.SourceDescription:\\n\" +\n\t\t\t\t\"\\tChunks:\\n\" +\n\t\t\t\t\"\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\tSource: 268435456\\n\" +\n\t\t\t\t\"\\t\\t\\tItems:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tType: [CNAME]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tText: A\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t1:\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tType: [PHONE]\\n\" +\n\t\t\t\t\"\\t\\t\\t\\t\\tText: B\\n\",\n\t\t},\n\t\t{\n\t\t\t&TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  5,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          1124282272,\n\t\t\t\tBaseSequenceNumber: 153,\n\t\t\t\tPacketStatusCount:  1,\n\t\t\t\tReferenceTime:      4057090,\n\t\t\t\tFbPktCount:         23,\n\t\t\t\t// 0b00100000, 0b00000001\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&RunLengthChunk{\n\t\t\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tRunLength:          1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t// 0b10010100\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 37000,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"rtcp.TransportLayerCC:\\n\" +\n\t\t\t\t\"\\tHeader:\\n\" +\n\t\t\t\t\"\\t\\tPadding: true\\n\" +\n\t\t\t\t\"\\t\\tCount: 15\\n\" +\n\t\t\t\t\"\\t\\tType: [TSFB]\\n\" +\n\t\t\t\t\"\\t\\tLength: 5\\n\" +\n\t\t\t\t\"\\tSenderSSRC: 4195875351\\n\" +\n\t\t\t\t\"\\tMediaSSRC: 1124282272\\n\" +\n\t\t\t\t\"\\tBaseSequenceNumber: 153\\n\" +\n\t\t\t\t\"\\tPacketStatusCount: 1\\n\" +\n\t\t\t\t\"\\tReferenceTime: 4057090\\n\" +\n\t\t\t\t\"\\tFbPktCount: 23\\n\" +\n\t\t\t\t\"\\tPacketChunks:\\n\" +\n\t\t\t\t\"\\t\\t0 (rtcp.RunLengthChunk):\\n\" +\n\t\t\t\t\"\\t\\t\\tPacketStatusChunk: <nil>\\n\" +\n\t\t\t\t\"\\t\\t\\tType: 0\\n\" +\n\t\t\t\t\"\\t\\t\\tPacketStatusSymbol: 1\\n\" +\n\t\t\t\t\"\\t\\t\\tRunLength: 1\\n\" +\n\t\t\t\t\"\\tRecvDeltas:\\n\" +\n\t\t\t\t\"\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\tType: 1\\n\" +\n\t\t\t\t\"\\t\\t\\tDelta: 37000\\n\",\n\t\t},\n\t\t{\n\t\t\t&TransportLayerNack{\n\t\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t\t\tNacks:      []NackPair{{1, 0xAA}, {1034, 0x05}},\n\t\t\t},\n\t\t\t\"rtcp.TransportLayerNack:\\n\" +\n\t\t\t\t\"\\tSenderSSRC: 2419039790\\n\" +\n\t\t\t\t\"\\tMediaSSRC: 2419039790\\n\" +\n\t\t\t\t\"\\tNacks:\\n\" +\n\t\t\t\t\"\\t\\t0:\\n\" +\n\t\t\t\t\"\\t\\t\\tPacketID: 1\\n\" +\n\t\t\t\t\"\\t\\t\\tLostPackets: 170\\n\" +\n\t\t\t\t\"\\t\\t1:\\n\" +\n\t\t\t\t\"\\t\\t\\tPacketID: 1034\\n\" +\n\t\t\t\t\"\\t\\t\\tLostPackets: 5\\n\",\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\tassert.Equalf(t, test.expected, stringify(test.packet), \"Error stringifying test %d\", i)\n\t}\n}\n"
  },
  {
    "path": "packet_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// An RTCP packet from a packet dump.\nfunc realPacket() []byte {\n\treturn []byte{\n\t\t// Receiver Report (offset=0)\n\t\t// v=2, p=0, count=1, RR, len=7\n\t\t0x81, 0xc9, 0x0, 0x7,\n\t\t// ssrc=0x902f9e2e\n\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t// ssrc=0xbc5e9a40\n\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t// fracLost=0, totalLost=0\n\t\t0x0, 0x0, 0x0, 0x0,\n\t\t// lastSeq=0x46e1\n\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t// jitter=273\n\t\t0x0, 0x0, 0x1, 0x11,\n\t\t// lsr=0x9f36432\n\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t// delay=150137\n\t\t0x0, 0x2, 0x4a, 0x79,\n\n\t\t// Source Description (offset=32)\n\t\t// v=2, p=0, count=1, SDES, len=12\n\t\t0x81, 0xca, 0x0, 0xc,\n\t\t// ssrc=0x902f9e2e\n\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t// CNAME, len=38\n\t\t0x1, 0x26,\n\t\t// text=\"{9c00eb92-1afb-9d49-a47d-91f64eee69f5}\"\n\t\t0x7b, 0x39, 0x63, 0x30,\n\t\t0x30, 0x65, 0x62, 0x39,\n\t\t0x32, 0x2d, 0x31, 0x61,\n\t\t0x66, 0x62, 0x2d, 0x39,\n\t\t0x64, 0x34, 0x39, 0x2d,\n\t\t0x61, 0x34, 0x37, 0x64,\n\t\t0x2d, 0x39, 0x31, 0x66,\n\t\t0x36, 0x34, 0x65, 0x65,\n\t\t0x65, 0x36, 0x39, 0x66,\n\t\t0x35, 0x7d,\n\t\t// END + padding\n\t\t0x0, 0x0, 0x0, 0x0,\n\n\t\t// Goodbye (offset=84)\n\t\t// v=2, p=0, count=1, BYE, len=1\n\t\t0x81, 0xcb, 0x0, 0x1,\n\t\t// source=0x902f9e2e\n\t\t0x90, 0x2f, 0x9e, 0x2e,\n\n\t\t// Picture Loss Indication (offset=92)\n\t\t0x81, 0xce, 0x0, 0x2,\n\t\t// sender=0x902f9e2e\n\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t// media=0x902f9e2e\n\t\t0x90, 0x2f, 0x9e, 0x2e,\n\n\t\t// RapidResynchronizationRequest (offset=104)\n\t\t0x85, 0xcd, 0x0, 0x2,\n\t\t// sender=0x902f9e2e\n\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t// media=0x902f9e2e\n\t\t0x90, 0x2f, 0x9e, 0x2e,\n\n\t\t// ApplicationDefined (offset=116)\n\t\t0x80, 0xcc, 0x00, 0x03,\n\t\t// sender=0x4baae1ab\n\t\t0x4b, 0xaa, 0xe1, 0xab,\n\t\t// name='NAME'\n\t\t0x4E, 0x41, 0x4D, 0x45,\n\t\t// data='ABCD'\n\t\t0x41, 0x42, 0x43, 0x44,\n\t}\n}\n\nfunc TestUnmarshal(t *testing.T) {\n\tpacket, err := Unmarshal(realPacket())\n\tassert.NoError(t, err)\n\n\texpected := []Packet{\n\t\t&ReceiverReport{\n\t\t\tSSRC: 0x902f9e2e,\n\t\t\tReports: []ReceptionReport{{\n\t\t\t\tSSRC:               0xbc5e9a40,\n\t\t\t\tFractionLost:       0,\n\t\t\t\tTotalLost:          0,\n\t\t\t\tLastSequenceNumber: 0x46e1,\n\t\t\t\tJitter:             273,\n\t\t\t\tLastSenderReport:   0x9f36432,\n\t\t\t\tDelay:              150137,\n\t\t\t}},\n\t\t\tProfileExtensions: []byte{},\n\t\t},\n\t\tNewCNAMESourceDescription(0x902f9e2e, \"{9c00eb92-1afb-9d49-a47d-91f64eee69f5}\"),\n\t\t&Goodbye{\n\t\t\tSources: []uint32{0x902f9e2e},\n\t\t},\n\t\t&PictureLossIndication{\n\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t},\n\t\t&RapidResynchronizationRequest{\n\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t},\n\t\t&ApplicationDefined{\n\t\t\tSSRC: 0x4baae1ab,\n\t\t\tName: \"NAME\",\n\t\t\tData: []byte{0x41, 0x42, 0x43, 0x44},\n\t\t},\n\t}\n\n\tassert.Equal(t, expected, packet)\n}\n\nfunc TestUnmarshalNil(t *testing.T) {\n\t_, err := Unmarshal(nil)\n\tassert.ErrorIs(t, err, errInvalidHeader)\n}\n\nfunc TestInvalidHeaderLength(t *testing.T) {\n\tinvalidPacket := []byte{\n\t\t// Receiver Report (offset=0)\n\t\t// v=2, p=0, count=1, RR, len=100\n\t\t0x81, 0xc9, 0x0, 0x64,\n\t}\n\n\t_, err := Unmarshal(invalidPacket)\n\tassert.ErrorIs(t, err, errPacketTooShort)\n}\n"
  },
  {
    "path": "picture_loss_indication.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\n// The PictureLossIndication packet informs the encoder about the loss of an undefined amount of\n// coded video data belonging to one or more pictures.\ntype PictureLossIndication struct {\n\t// SSRC of sender\n\tSenderSSRC uint32\n\n\t// SSRC where the loss was experienced\n\tMediaSSRC uint32\n}\n\nconst (\n\tpliLength = 2\n)\n\n// Marshal encodes the PictureLossIndication in binary.\nfunc (p PictureLossIndication) Marshal() ([]byte, error) {\n\t/*\n\t * PLI does not require parameters.  Therefore, the length field MUST be\n\t * 2, and there MUST NOT be any Feedback Control Information.\n\t *\n\t * The semantics of this FB message is independent of the payload type.\n\t */\n\trawPacket := make([]byte, p.MarshalSize())\n\tpacketBody := rawPacket[headerLength:]\n\n\tbinary.BigEndian.PutUint32(packetBody, p.SenderSSRC)\n\tbinary.BigEndian.PutUint32(packetBody[4:], p.MediaSSRC)\n\n\th := Header{\n\t\tCount:  FormatPLI,\n\t\tType:   TypePayloadSpecificFeedback,\n\t\tLength: pliLength,\n\t}\n\thData, err := h.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcopy(rawPacket, hData)\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the PictureLossIndication from binary.\nfunc (p *PictureLossIndication) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) < (headerLength + (ssrcLength * 2)) {\n\t\treturn errPacketTooShort\n\t}\n\n\tvar h Header\n\tif err := h.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\n\tif h.Type != TypePayloadSpecificFeedback || h.Count != FormatPLI {\n\t\treturn errWrongType\n\t}\n\n\tp.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])\n\tp.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])\n\n\treturn nil\n}\n\n// Header returns the Header associated with this packet.\nfunc (p *PictureLossIndication) Header() Header {\n\treturn Header{\n\t\tCount:  FormatPLI,\n\t\tType:   TypePayloadSpecificFeedback,\n\t\tLength: pliLength,\n\t}\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (p *PictureLossIndication) MarshalSize() int {\n\treturn headerLength + ssrcLength*2\n}\n\nfunc (p *PictureLossIndication) String() string {\n\treturn fmt.Sprintf(\"PictureLossIndication %x %x\", p.SenderSSRC, p.MediaSSRC)\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (p *PictureLossIndication) DestinationSSRC() []uint32 {\n\treturn []uint32{p.MediaSSRC}\n}\n"
  },
  {
    "path": "picture_loss_indication_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*PictureLossIndication)(nil) // assert is a Packet\n\nfunc TestPictureLossIndicationUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      PictureLossIndication\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, FMT=1, PSFB, len=1\n\t\t\t\t0x81, 0xce, 0x00, 0x02,\n\t\t\t\t// ssrc=0x0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x4bc4fcb4\n\t\t\t\t0x4b, 0xc4, 0xfc, 0xb4,\n\t\t\t},\n\t\t\tWant: PictureLossIndication{\n\t\t\t\tSenderSSRC: 0x0,\n\t\t\t\tMediaSSRC:  0x4bc4fcb4,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"packet too short\",\n\t\t\tData: []byte{\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"invalid header\",\n\t\t\tData: []byte{\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWantError: errBadVersion,\n\t\t},\n\t\t{\n\t\t\tName: \"wrong type\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, FMT=1, RR, len=1\n\t\t\t\t0x81, 0xc9, 0x00, 0x02,\n\t\t\t\t// ssrc=0x0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x4bc4fcb4\n\t\t\t\t0x4b, 0xc4, 0xfc, 0xb4,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t\t{\n\t\t\tName: \"wrong fmt\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, FMT=2, RR, len=1\n\t\t\t\t0x82, 0xc9, 0x00, 0x02,\n\t\t\t\t// ssrc=0x0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x4bc4fcb4\n\t\t\t\t0x4b, 0xc4, 0xfc, 0xb4,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t} {\n\t\tvar pli PictureLossIndication\n\t\terr := pli.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equalf(t, test.Want, pli, \"Unmarshal %q\", test.Name)\n\t}\n}\n\nfunc TestPictureLossIndicationRoundTrip(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tPacket    PictureLossIndication\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tPacket: PictureLossIndication{\n\t\t\t\tSenderSSRC: 1,\n\t\t\t\tMediaSSRC:  2,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"also valid\",\n\t\t\tPacket: PictureLossIndication{\n\t\t\t\tSenderSSRC: 5000,\n\t\t\t\tMediaSSRC:  6000,\n\t\t\t},\n\t\t},\n\t} {\n\t\tdata, err := test.Packet.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar decoded PictureLossIndication\n\t\tassert.NoErrorf(t, decoded.Unmarshal(data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Packet, decoded, \"%q rr round trip mismatch\", test.Name)\n\t}\n}\n\nfunc TestPictureLossIndicationUnmarshalHeader(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      Header\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid header\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, FMT=1, PSFB, len=1\n\t\t\t\t0x81, 0xce, 0x00, 0x02,\n\t\t\t\t// ssrc=0x0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// ssrc=0x4bc4fcb4\n\t\t\t\t0x4b, 0xc4, 0xfc, 0xb4,\n\t\t\t},\n\t\t\tWant: Header{\n\t\t\t\tCount:  FormatPLI,\n\t\t\t\tType:   TypePayloadSpecificFeedback,\n\t\t\t\tLength: pliLength,\n\t\t\t},\n\t\t},\n\t} {\n\t\tvar pli PictureLossIndication\n\t\terr := pli.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal header %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equalf(t, test.Want, pli.Header(), \"Unmarshal header %q\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "rapid_resynchronization_request.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\n// The RapidResynchronizationRequest packet informs the encoder about the loss of\n// an undefined amount of coded video data belonging to one or more pictures.\ntype RapidResynchronizationRequest struct {\n\t// SSRC of sender\n\tSenderSSRC uint32\n\n\t// SSRC of the media source\n\tMediaSSRC uint32\n}\n\n// RapidResynchronisationRequest is provided as RFC 6051 spells resynchronization with an s.\n// We provide both names to be consistent with other RFCs which spell resynchronization with a z.\ntype RapidResynchronisationRequest = RapidResynchronizationRequest\n\nconst (\n\trrrLength       = 2\n\trrrHeaderLength = ssrcLength * 2\n\trrrMediaOffset  = 4\n)\n\n// Marshal encodes the RapidResynchronizationRequest in binary.\nfunc (p RapidResynchronizationRequest) Marshal() ([]byte, error) {\n\t/*\n\t * RRR does not require parameters.  Therefore, the length field MUST be\n\t * 2, and there MUST NOT be any Feedback Control Information.\n\t *\n\t * The semantics of this FB message is independent of the payload type.\n\t */\n\trawPacket := make([]byte, p.MarshalSize())\n\tpacketBody := rawPacket[headerLength:]\n\n\tbinary.BigEndian.PutUint32(packetBody, p.SenderSSRC)\n\tbinary.BigEndian.PutUint32(packetBody[rrrMediaOffset:], p.MediaSSRC)\n\n\thData, err := p.Header().Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcopy(rawPacket, hData)\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the RapidResynchronizationRequest from binary.\nfunc (p *RapidResynchronizationRequest) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) < (headerLength + (ssrcLength * 2)) {\n\t\treturn errPacketTooShort\n\t}\n\n\tvar h Header\n\tif err := h.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\n\tif h.Type != TypeTransportSpecificFeedback || h.Count != FormatRRR {\n\t\treturn errWrongType\n\t}\n\n\tp.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])\n\tp.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])\n\n\treturn nil\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (p *RapidResynchronizationRequest) MarshalSize() int {\n\treturn headerLength + rrrHeaderLength\n}\n\n// Header returns the Header associated with this packet.\nfunc (p *RapidResynchronizationRequest) Header() Header {\n\treturn Header{\n\t\tCount:  FormatRRR,\n\t\tType:   TypeTransportSpecificFeedback,\n\t\tLength: rrrLength,\n\t}\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (p *RapidResynchronizationRequest) DestinationSSRC() []uint32 {\n\treturn []uint32{p.MediaSSRC}\n}\n\nfunc (p *RapidResynchronizationRequest) String() string {\n\treturn fmt.Sprintf(\"RapidResynchronizationRequest %x %x\", p.SenderSSRC, p.MediaSSRC)\n}\n"
  },
  {
    "path": "rapid_resynchronization_request_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*RapidResynchronizationRequest)(nil) // assert is a Packet\n\nfunc TestRapidResynchronizationRequestUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      RapidResynchronizationRequest\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tData: []byte{\n\t\t\t\t// RapidResynchronizationRequest\n\t\t\t\t0x85, 0xcd, 0x0, 0x2,\n\t\t\t\t// sender=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// media=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t},\n\t\t\tWant: RapidResynchronizationRequest{\n\t\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"short report\",\n\t\t\tData: []byte{\n\t\t\t\t0x85, 0xcd, 0x0, 0x2,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// report ends early\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"wrong type\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SR, len=7\n\t\t\t\t0x81, 0xc8, 0x0, 0x7,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// ssrc=0xbc5e9a40\n\t\t\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t// lastSeq=0x46e1\n\t\t\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t\t\t// jitter=273\n\t\t\t\t0x0, 0x0, 0x1, 0x11,\n\t\t\t\t// lsr=0x9f36432\n\t\t\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t\t\t// delay=150137\n\t\t\t\t0x0, 0x2, 0x4a, 0x79,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t\t{\n\t\t\tName:      \"nil\",\n\t\t\tData:      nil,\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t} {\n\t\tvar rrr RapidResynchronizationRequest\n\t\terr := rrr.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tassert.Equalf(t, test.Want, rrr, \"Unmarshal %q\", test.Name)\n\t}\n}\n\nfunc TestRapidResynchronizationRequestRoundTrip(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tReport    RapidResynchronizationRequest\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tReport: RapidResynchronizationRequest{\n\t\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t\t},\n\t\t},\n\t} {\n\t\tdata, err := test.Report.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar decoded RapidResynchronizationRequest\n\t\tassert.NoErrorf(t, decoded.Unmarshal(data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Report, decoded, \"%q round trip\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "raw_packet.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport \"fmt\"\n\n// RawPacket represents an unparsed RTCP packet. It's returned by Unmarshal when\n// a packet with an unknown type is encountered.\ntype RawPacket []byte\n\n// Marshal encodes the packet in binary.\nfunc (r RawPacket) Marshal() ([]byte, error) {\n\treturn r, nil\n}\n\n// Unmarshal decodes the packet from binary.\nfunc (r *RawPacket) Unmarshal(b []byte) error {\n\tif len(b) < (headerLength) {\n\t\treturn errPacketTooShort\n\t}\n\t*r = b\n\n\tvar h Header\n\n\treturn h.Unmarshal(b)\n}\n\n// Header returns the Header associated with this packet.\nfunc (r RawPacket) Header() Header {\n\tvar h Header\n\tif err := h.Unmarshal(r); err != nil {\n\t\treturn Header{}\n\t}\n\n\treturn h\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (r *RawPacket) DestinationSSRC() []uint32 {\n\treturn []uint32{}\n}\n\nfunc (r RawPacket) String() string {\n\tout := fmt.Sprintf(\"RawPacket: %v\", ([]byte)(r))\n\n\treturn out\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (r RawPacket) MarshalSize() int {\n\treturn len(r)\n}\n"
  },
  {
    "path": "raw_packet_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*RawPacket)(nil) // assert is a Packet\n\nfunc TestRawPacketRoundTrip(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName               string\n\t\tPacket             RawPacket\n\t\tWantMarshalError   error\n\t\tWantUnmarshalError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tPacket: RawPacket([]byte{\n\t\t\t\t// v=2, p=0, count=1, BYE, len=12\n\t\t\t\t0x81, 0xcb, 0x00, 0x0c,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// len=3, text=FOO\n\t\t\t\t0x03, 0x46, 0x4f, 0x4f,\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tName:               \"short header\",\n\t\t\tPacket:             RawPacket([]byte{0x00}),\n\t\t\tWantUnmarshalError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"invalid header\",\n\t\t\tPacket: RawPacket([]byte{\n\t\t\t\t// v=0, p=0, count=0, RR, len=4\n\t\t\t\t0x00, 0xc9, 0x00, 0x04,\n\t\t\t}),\n\t\t\tWantUnmarshalError: errBadVersion,\n\t\t},\n\t} {\n\t\tdata, err := test.Packet.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantMarshalError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar decoded RawPacket\n\n\t\terr = decoded.Unmarshal(data)\n\t\tassert.ErrorIsf(t, err, test.WantUnmarshalError, \"Unmarshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tassert.Equalf(t, test.Packet, decoded, \"Unmarshal %q\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "receiver_estimated_maximum_bitrate.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"math\"\n)\n\n// ReceiverEstimatedMaximumBitrate contains the receiver's estimated maximum bitrate.\n// see: https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03\ntype ReceiverEstimatedMaximumBitrate struct {\n\t// SSRC of sender\n\tSenderSSRC uint32\n\n\t// Estimated maximum bitrate\n\tBitrate float32\n\n\t// SSRC entries which this packet applies to\n\tSSRCs []uint32\n}\n\n// Marshal serializes the packet and returns a byte slice.\nfunc (p ReceiverEstimatedMaximumBitrate) Marshal() (buf []byte, err error) {\n\t// Allocate a buffer of the exact output size.\n\tbuf = make([]byte, p.MarshalSize())\n\n\t// Write to our buffer.\n\tn, err := p.MarshalTo(buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// This will always be true but just to be safe.\n\tif n != len(buf) {\n\t\treturn nil, errWrongMarshalSize\n\t}\n\n\treturn buf, nil\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (p ReceiverEstimatedMaximumBitrate) MarshalSize() int {\n\treturn 20 + 4*len(p.SSRCs)\n}\n\n// MarshalTo serializes the packet to the given byte slice.\nfunc (p ReceiverEstimatedMaximumBitrate) MarshalTo(buf []byte) (n int, err error) {\n\tconst bitratemax = 0x3FFFFp+63\n\t/*\n\t    0                   1                   2                   3\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   |V=2|P| FMT=15  |   PT=206      |             length            |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |                  SSRC of packet sender                        |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |                  SSRC of media source                         |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |  Unique identifier 'R' 'E' 'M' 'B'                            |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |  Num SSRC     | BR Exp    |  BR Mantissa                      |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |   SSRC feedback                                               |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |  ...                                                          |\n\t*/\n\n\tsize := p.MarshalSize()\n\tif len(buf) < size {\n\t\treturn 0, errPacketTooShort\n\t}\n\n\tbuf[0] = 143 // v=2, p=0, fmt=15\n\tbuf[1] = 206\n\n\t// Length of this packet in 32-bit words minus one.\n\tlength := uint16((p.MarshalSize() / 4) - 1) //nolint:gosec // G115\n\tbinary.BigEndian.PutUint16(buf[2:4], length)\n\n\tbinary.BigEndian.PutUint32(buf[4:8], p.SenderSSRC)\n\tbinary.BigEndian.PutUint32(buf[8:12], 0) // always zero\n\n\t// ALL HAIL REMB\n\tbuf[12] = 'R'\n\tbuf[13] = 'E'\n\tbuf[14] = 'M'\n\tbuf[15] = 'B'\n\n\t// Write the length of the ssrcs to follow at the end\n\tif len(p.SSRCs) > math.MaxUint8 {\n\t\treturn 0, errTooManySSRCs\n\t}\n\n\tbuf[16] = byte(len(p.SSRCs)) //nolint:gosec // length validated above\n\n\texp := 0\n\tbitrate := p.Bitrate\n\n\tif bitrate >= bitratemax {\n\t\tbitrate = bitratemax\n\t}\n\n\tif bitrate < 0 {\n\t\treturn 0, errInvalidBitrate\n\t}\n\n\tfor bitrate >= (1 << 18) {\n\t\tbitrate /= 2.0\n\t\texp++\n\t}\n\n\tif exp >= (1 << 6) {\n\t\treturn 0, errInvalidBitrate\n\t}\n\n\tmantissa := uint(math.Floor(float64(bitrate)))\n\n\t// We can't quite use the binary package because\n\t// a) it's a uint24 and b) the exponent is only 6-bits\n\t// Just trust me; this is big-endian encoding.\n\tbuf[17] = byte(exp<<2) | byte(mantissa>>16) //nolint: gosec //  mantissa is limited to 18 bits\n\tbuf[18] = byte(mantissa >> 8)               //nolint: gosec //  mantissa is limited to 18 bits\n\tbuf[19] = byte(mantissa)                    // nolint: gosec //  mantissa is limited to 18 bits\n\n\t// Write the SSRCs at the very end.\n\tn = 20\n\tfor _, ssrc := range p.SSRCs {\n\t\tbinary.BigEndian.PutUint32(buf[n:n+4], ssrc)\n\t\tn += 4\n\t}\n\n\treturn n, nil\n}\n\n// Unmarshal reads a REMB packet from the given byte slice.\n//\n//nolint:cyclop\nfunc (p *ReceiverEstimatedMaximumBitrate) Unmarshal(buf []byte) (err error) {\n\tconst mantissamax = 0x7FFFFF\n\t/*\n\t    0                   1                   2                   3\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   |V=2|P| FMT=15  |   PT=206      |             length            |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |                  SSRC of packet sender                        |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |                  SSRC of media source                         |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |  Unique identifier 'R' 'E' 'M' 'B'                            |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |  Num SSRC     | BR Exp    |  BR Mantissa                      |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |   SSRC feedback                                               |\n\t   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t   |  ...                                                          |\n\t*/\n\n\t// 20 bytes is the size of the packet with no SSRCs\n\tif len(buf) < 20 {\n\t\treturn errPacketTooShort\n\t}\n\n\t// version  must be 2\n\tversion := buf[0] >> 6\n\tif version != 2 {\n\t\treturn fmt.Errorf(\"%w expected(2) actual(%d)\", errBadVersion, version)\n\t}\n\n\t// padding must be unset\n\tpadding := (buf[0] >> 5) & 1\n\tif padding != 0 {\n\t\treturn fmt.Errorf(\"%w expected(0) actual(%d)\", errWrongPadding, padding)\n\t}\n\n\t// fmt must be 15\n\tfmtVal := buf[0] & 31\n\tif fmtVal != 15 {\n\t\treturn fmt.Errorf(\"%w expected(15) actual(%d)\", errWrongFeedbackType, fmtVal)\n\t}\n\n\t// Must be payload specific feedback\n\tif buf[1] != 206 {\n\t\treturn fmt.Errorf(\"%w expected(206) actual(%d)\", errWrongPayloadType, buf[1])\n\t}\n\n\t// length is the number of 32-bit words, minus 1\n\tlength := binary.BigEndian.Uint16(buf[2:4])\n\tsize := int((length + 1) * 4)\n\n\t// There's not way this could be legit\n\tif size < 20 {\n\t\treturn errHeaderTooSmall\n\t}\n\n\t// Make sure the buffer is large enough.\n\tif len(buf) < size {\n\t\treturn errPacketTooShort\n\t}\n\n\t// The sender SSRC is 32-bits\n\tp.SenderSSRC = binary.BigEndian.Uint32(buf[4:8])\n\n\t// The destination SSRC must be 0\n\tmedia := binary.BigEndian.Uint32(buf[8:12])\n\tif media != 0 {\n\t\treturn errSSRCMustBeZero\n\t}\n\n\t// REMB rules all around me\n\tif !bytes.Equal(buf[12:16], []byte{'R', 'E', 'M', 'B'}) {\n\t\treturn errMissingREMBidentifier\n\t}\n\n\t// The next byte is the number of SSRC entries at the end.\n\tnum := int(buf[16])\n\n\t// Now we know the expected size, make sure they match.\n\tif size != 20+4*num {\n\t\treturn errSSRCNumAndLengthMismatch\n\t}\n\n\t// Get the 6-bit exponent value.\n\texp := buf[17] >> 2\n\texp += 127 // bias for IEEE754\n\texp += 23  // IEEE754 biases the decimal to the left, abs-send-time biases it to the right\n\n\t// The remaining 2-bits plus the next 16-bits are the mantissa.\n\tmantissa := uint32(buf[17]&3)<<16 | uint32(buf[18])<<8 | uint32(buf[19])\n\n\tif mantissa != 0 {\n\t\t// ieee754 requires an implicit leading bit\n\t\tfor (mantissa & (mantissamax + 1)) == 0 {\n\t\t\texp--\n\t\t\tmantissa *= 2\n\t\t}\n\t}\n\n\t// bitrate = mantissa * 2^exp\n\tp.Bitrate = math.Float32frombits((uint32(exp) << 23) | (mantissa & mantissamax))\n\n\t// Clear any existing SSRCs\n\tp.SSRCs = nil\n\n\t// Loop over and parse the SSRC entires at the end.\n\t// We already verified that size == num * 4\n\tfor n := 20; n < size; n += 4 {\n\t\tssrc := binary.BigEndian.Uint32(buf[n : n+4])\n\t\tp.SSRCs = append(p.SSRCs, ssrc)\n\t}\n\n\treturn nil\n}\n\n// Header returns the Header associated with this packet.\nfunc (p *ReceiverEstimatedMaximumBitrate) Header() Header {\n\treturn Header{\n\t\tCount:  FormatREMB,\n\t\tType:   TypePayloadSpecificFeedback,\n\t\tLength: uint16((p.MarshalSize() / 4) - 1), //nolint:gosec // G115\n\t}\n}\n\n// String prints the REMB packet in a human-readable format.\nfunc (p *ReceiverEstimatedMaximumBitrate) String() string {\n\t// Keep a table of powers to units for fast conversion.\n\tbitUnits := []string{\"b\", \"Kb\", \"Mb\", \"Gb\", \"Tb\", \"Pb\", \"Eb\"}\n\n\t// Do some unit conversions because b/s is far too difficult to read.\n\tbitrate := p.Bitrate\n\tpowers := 0\n\n\t// Keep dividing the bitrate until it's under 1000\n\tfor bitrate >= 1000.0 && powers < len(bitUnits) {\n\t\tbitrate /= 1000.0\n\t\tpowers++\n\t}\n\n\tunit := bitUnits[powers] //nolint:gosec // powers is bounded by loop condition\n\n\treturn fmt.Sprintf(\"ReceiverEstimatedMaximumBitrate %x %.2f %s/s\", p.SenderSSRC, bitrate, unit)\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (p *ReceiverEstimatedMaximumBitrate) DestinationSSRC() []uint32 {\n\treturn p.SSRCs\n}\n"
  },
  {
    "path": "receiver_estimated_maximum_bitrate_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*ReceiverEstimatedMaximumBitrate)(nil) // assert is a Packet\n\nfunc TestReceiverEstimatedMaximumBitrateMarshal(t *testing.T) {\n\tassert := assert.New(t)\n\n\tinput := ReceiverEstimatedMaximumBitrate{\n\t\tSenderSSRC: 1,\n\t\tBitrate:    8927168.0,\n\t\tSSRCs:      []uint32{1215622422},\n\t}\n\n\texpected := []byte{143, 206, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 82, 69, 77, 66, 1, 26, 32, 223, 72, 116, 237, 22}\n\n\toutput, err := input.Marshal()\n\tassert.NoError(err)\n\tassert.Equal(expected, output)\n}\n\nfunc TestReceiverEstimatedMaximumBitrateUnmarshal(t *testing.T) {\n\tassert := assert.New(t)\n\n\t// Real data sent by Chrome while watching a 6Mb/s stream\n\tinput := []byte{143, 206, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 82, 69, 77, 66, 1, 26, 32, 223, 72, 116, 237, 22}\n\n\t// mantissa = []byte{26 & 3, 32, 223} = []byte{2, 32, 223} = 139487\n\t// exp = 26 >> 2 = 6\n\t// bitrate = 139487 * 2^6 = 139487 * 64 = 8927168 = 8.9 Mb/s\n\texpected := ReceiverEstimatedMaximumBitrate{\n\t\tSenderSSRC: 1,\n\t\tBitrate:    8927168,\n\t\tSSRCs:      []uint32{1215622422},\n\t}\n\n\tpacket := ReceiverEstimatedMaximumBitrate{}\n\terr := packet.Unmarshal(input)\n\tassert.NoError(err)\n\tassert.Equal(expected, packet)\n}\n\nfunc TestReceiverEstimatedMaximumBitrateTruncate(t *testing.T) {\n\tassert := assert.New(t)\n\n\tinput := []byte{143, 206, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 82, 69, 77, 66, 1, 26, 32, 223, 72, 116, 237, 22}\n\n\t// Make sure that we're interpreting the bitrate correctly.\n\t// For the above example, we have:\n\n\t// mantissa = 139487\n\t// exp = 6\n\t// bitrate = 8927168\n\n\tpacket := ReceiverEstimatedMaximumBitrate{}\n\terr := packet.Unmarshal(input)\n\tassert.NoError(err)\n\tassert.Equal(float32(8927168), packet.Bitrate)\n\n\t// Just verify marshal produces the same input.\n\toutput, err := packet.Marshal()\n\tassert.NoError(err)\n\tassert.Equal(input, output)\n\n\t// If we subtract the bitrate by 1, we'll round down a lower mantissa\n\tpacket.Bitrate--\n\n\t// bitrate = 8927167\n\t// mantissa = 139486\n\t// exp = 6\n\n\toutput, err = packet.Marshal()\n\tassert.NoError(err)\n\tassert.NotEqual(input, output)\n\n\t// Which if we actually unmarshal again, we'll find that it's actually decreased by 63 (which is exp)\n\t// mantissa = 139486\n\t// exp = 6\n\t// bitrate = 8927104\n\n\terr = packet.Unmarshal(output)\n\tassert.NoError(err)\n\tassert.Equal(float32(8927104), packet.Bitrate)\n}\n\nfunc TestReceiverEstimatedMaximumBitrateOverflow(t *testing.T) {\n\tassert := assert.New(t)\n\n\t// Marshal a packet with the maximum possible bitrate.\n\tpacket := ReceiverEstimatedMaximumBitrate{\n\t\tBitrate: math.MaxFloat32,\n\t}\n\n\t// mantissa = 262143 = 0x3FFFF\n\t// exp = 63\n\n\texpected := []byte{143, 206, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 82, 69, 77, 66, 0, 255, 255, 255}\n\n\toutput, err := packet.Marshal()\n\tassert.NoError(err)\n\tassert.Equal(expected, output)\n\n\t// mantissa = 262143\n\t// exp = 63\n\t// bitrate = 0xFFFFC00000000000\n\n\terr = packet.Unmarshal(output)\n\tassert.NoError(err)\n\tassert.Equal(math.Float32frombits(0x67FFFFC0), packet.Bitrate)\n\n\t// Make sure we marshal to the same result again.\n\toutput, err = packet.Marshal()\n\tassert.NoError(err)\n\tassert.Equal(expected, output)\n\n\t// Finally, try unmarshalling one number higher than we used to be able to handle.\n\tinput := []byte{143, 206, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 82, 69, 77, 66, 0, 188, 0, 0}\n\terr = packet.Unmarshal(input)\n\tassert.NoError(err)\n\tassert.Equal(math.Float32frombits(0x62800000), packet.Bitrate)\n}\n"
  },
  {
    "path": "receiver_report.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"strings\"\n)\n\n// A ReceiverReport (RR) packet provides reception quality feedback for an RTP stream.\ntype ReceiverReport struct {\n\t// The synchronization source identifier for the originator of this RR packet.\n\tSSRC uint32\n\t// Zero or more reception report blocks depending on the number of other\n\t// sources heard by this sender since the last report. Each reception report\n\t// block conveys statistics on the reception of RTP packets from a\n\t// single synchronization source.\n\tReports []ReceptionReport\n\t// Extension contains additional, payload-specific information that needs to\n\t// be reported regularly about the receiver.\n\tProfileExtensions []byte\n}\n\nconst (\n\tssrcLength     = 4\n\trrSSRCOffset   = headerLength\n\trrReportOffset = rrSSRCOffset + ssrcLength\n)\n\n// Marshal encodes the ReceiverReport in binary.\nfunc (r ReceiverReport) Marshal() ([]byte, error) {\n\t/*\n\t *         0                   1                   2                   3\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 * header |V=2|P|    RC   |   PT=RR=201   |             length            |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                     SSRC of packet sender                     |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * report |                 SSRC_1 (SSRC of first source)                 |\n\t * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *   1    | fraction lost |       cumulative number of packets lost       |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |           extended highest sequence number received           |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                      interarrival jitter                      |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                         last SR (LSR)                         |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                   delay since last SR (DLSR)                  |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * report |                 SSRC_2 (SSRC of second source)                |\n\t * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *   2    :                               ...                             :\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t *        |                  profile-specific extensions                  |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\n\trawPacket := make([]byte, r.MarshalSize())\n\tpacketBody := rawPacket[headerLength:]\n\n\tbinary.BigEndian.PutUint32(packetBody, r.SSRC)\n\n\tfor i, rp := range r.Reports {\n\t\tdata, err := rp.Marshal()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\toffset := ssrcLength + receptionReportLength*i\n\t\tcopy(packetBody[offset:], data)\n\t}\n\n\tif len(r.Reports) > countMax {\n\t\treturn nil, errTooManyReports\n\t}\n\n\tpe := make([]byte, len(r.ProfileExtensions))\n\tcopy(pe, r.ProfileExtensions)\n\n\t// if the length of the profile extensions isn't devisible\n\t// by 4, we need to pad the end.\n\tfor (len(pe) & 0x3) != 0 {\n\t\tpe = append(pe, 0) //nolint:makezero\n\t}\n\n\trawPacket = append(rawPacket, pe...) //nolint:makezero\n\n\thData, err := r.Header().Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcopy(rawPacket, hData)\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the ReceiverReport from binary.\nfunc (r *ReceiverReport) Unmarshal(rawPacket []byte) error {\n\t/*\n\t *         0                   1                   2                   3\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 * header |V=2|P|    RC   |   PT=RR=201   |             length            |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                     SSRC of packet sender                     |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * report |                 SSRC_1 (SSRC of first source)                 |\n\t * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *   1    | fraction lost |       cumulative number of packets lost       |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |           extended highest sequence number received           |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                      interarrival jitter                      |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                         last SR (LSR)                         |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                   delay since last SR (DLSR)                  |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * report |                 SSRC_2 (SSRC of second source)                |\n\t * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *   2    :                               ...                             :\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t *        |                  profile-specific extensions                  |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\n\tif len(rawPacket) < (headerLength + ssrcLength) {\n\t\treturn errPacketTooShort\n\t}\n\n\tvar header Header\n\tif err := header.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\n\tif header.Type != TypeReceiverReport {\n\t\treturn errWrongType\n\t}\n\n\tr.SSRC = binary.BigEndian.Uint32(rawPacket[rrSSRCOffset:])\n\n\tfor i := rrReportOffset; i < len(rawPacket) && len(r.Reports) < int(header.Count); i += receptionReportLength {\n\t\tvar rr ReceptionReport\n\t\tif err := rr.Unmarshal(rawPacket[i:]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Reports = append(r.Reports, rr)\n\t}\n\tr.ProfileExtensions = rawPacket[rrReportOffset+(len(r.Reports)*receptionReportLength):]\n\n\t//nolint:gosec // G115\n\tif uint8(len(r.Reports)) != header.Count {\n\t\treturn errInvalidHeader\n\t}\n\n\treturn nil\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (r *ReceiverReport) MarshalSize() int {\n\trepsLength := 0\n\tfor _, rep := range r.Reports {\n\t\trepsLength += rep.len()\n\t}\n\n\treturn headerLength + ssrcLength + repsLength\n}\n\n// Header returns the Header associated with this packet.\nfunc (r *ReceiverReport) Header() Header {\n\treturn Header{\n\t\tCount:  uint8(len(r.Reports)), //nolint:gosec // G115\n\t\tType:   TypeReceiverReport,\n\t\tLength: uint16((r.MarshalSize()/4)-1) + uint16(getPadding(len(r.ProfileExtensions))), //nolint:gosec // G115\n\t}\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (r *ReceiverReport) DestinationSSRC() []uint32 {\n\tout := make([]uint32, len(r.Reports))\n\tfor i, v := range r.Reports {\n\t\tout[i] = v.SSRC\n\t}\n\n\treturn out\n}\n\nfunc (r ReceiverReport) String() string {\n\tvar out strings.Builder\n\tfmt.Fprintf(&out, \"ReceiverReport from %x\\n\", r.SSRC)\n\tout.WriteString(\"\\tSSRC    \\tLost\\tLastSequence\\n\")\n\tfor _, i := range r.Reports {\n\t\tfmt.Fprintf(&out, \"\\t%x\\t%d/%d\\t%d\\n\", i.SSRC, i.FractionLost, i.TotalLost, i.LastSequenceNumber)\n\t}\n\tfmt.Fprintf(&out, \"\\tProfile Extension Data: %v\\n\", r.ProfileExtensions)\n\n\treturn out.String()\n}\n"
  },
  {
    "path": "receiver_report_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*ReceiverReport)(nil) // assert is a Packet\n\nfunc TestReceiverReportUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      ReceiverReport\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, RR, len=7\n\t\t\t\t0x81, 0xc9, 0x0, 0x7,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// ssrc=0xbc5e9a40\n\t\t\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t// lastSeq=0x46e1\n\t\t\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t\t\t// jitter=273\n\t\t\t\t0x0, 0x0, 0x1, 0x11,\n\t\t\t\t// lsr=0x9f36432\n\t\t\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t\t\t// delay=150137\n\t\t\t\t0x0, 0x2, 0x4a, 0x79,\n\t\t\t},\n\t\t\tWant: ReceiverReport{\n\t\t\t\tSSRC: 0x902f9e2e,\n\t\t\t\tReports: []ReceptionReport{{\n\t\t\t\t\tSSRC:               0xbc5e9a40,\n\t\t\t\t\tFractionLost:       0,\n\t\t\t\t\tTotalLost:          0,\n\t\t\t\t\tLastSequenceNumber: 0x46e1,\n\t\t\t\t\tJitter:             273,\n\t\t\t\t\tLastSenderReport:   0x9f36432,\n\t\t\t\t\tDelay:              150137,\n\t\t\t\t}},\n\t\t\t\tProfileExtensions: []byte{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"valid with extension data\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, RR, len=9\n\t\t\t\t0x81, 0xc9, 0x0, 0x9,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// ssrc=0xbc5e9a40\n\t\t\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t// lastSeq=0x46e1\n\t\t\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t\t\t// jitter=273\n\t\t\t\t0x0, 0x0, 0x1, 0x11,\n\t\t\t\t// lsr=0x9f36432\n\t\t\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t\t\t// delay=150137\n\t\t\t\t0x0, 0x2, 0x4a, 0x79,\n\t\t\t\t// profile-specific extension data\n\t\t\t\t0x54, 0x45, 0x53, 0x54,\n\t\t\t\t0x44, 0x41, 0x54, 0x41,\n\t\t\t},\n\t\t\tWant: ReceiverReport{\n\t\t\t\tSSRC: 0x902f9e2e,\n\t\t\t\tReports: []ReceptionReport{{\n\t\t\t\t\tSSRC:               0xbc5e9a40,\n\t\t\t\t\tFractionLost:       0,\n\t\t\t\t\tTotalLost:          0,\n\t\t\t\t\tLastSequenceNumber: 0x46e1,\n\t\t\t\t\tJitter:             273,\n\t\t\t\t\tLastSenderReport:   0x9f36432,\n\t\t\t\t\tDelay:              150137,\n\t\t\t\t}},\n\t\t\t\tProfileExtensions: []byte{\n\t\t\t\t\t0x54, 0x45, 0x53, 0x54,\n\t\t\t\t\t0x44, 0x41, 0x54, 0x41,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"short report\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, RR, len=7\n\t\t\t\t0x81, 0xc9, 0x00, 0x0c,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// report ends early\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"wrong type\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SR, len=7\n\t\t\t\t0x81, 0xc8, 0x0, 0x7,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// ssrc=0xbc5e9a40\n\t\t\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t// lastSeq=0x46e1\n\t\t\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t\t\t// jitter=273\n\t\t\t\t0x0, 0x0, 0x1, 0x11,\n\t\t\t\t// lsr=0x9f36432\n\t\t\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t\t\t// delay=150137\n\t\t\t\t0x0, 0x2, 0x4a, 0x79,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t\t{\n\t\t\tName: \"bad count in header\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=2, RR, len=7\n\t\t\t\t0x82, 0xc9, 0x0, 0x7,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// ssrc=0xbc5e9a40\n\t\t\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t// lastSeq=0x46e1\n\t\t\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t\t\t// jitter=273\n\t\t\t\t0x0, 0x0, 0x1, 0x11,\n\t\t\t\t// lsr=0x9f36432\n\t\t\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t\t\t// delay=150137\n\t\t\t\t0x0, 0x2, 0x4a, 0x79,\n\t\t\t},\n\t\t\tWantError: errInvalidHeader,\n\t\t},\n\t\t{\n\t\t\tName:      \"nil\",\n\t\t\tData:      nil,\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t} {\n\t\tvar rr ReceiverReport\n\t\terr := rr.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equalf(t, test.Want, rr, \"Unmarshal %q\", test.Name)\n\t}\n}\n\nfunc tooManyReports() []ReceptionReport {\n\t// a slice with enough ReceptionReports to overflow an 5-bit int\n\tvar tooManyReports []ReceptionReport\n\tfor range 1 << 5 {\n\t\ttooManyReports = append(tooManyReports, ReceptionReport{\n\t\t\tSSRC:               2,\n\t\t\tFractionLost:       2,\n\t\t\tTotalLost:          3,\n\t\t\tLastSequenceNumber: 4,\n\t\t\tJitter:             5,\n\t\t\tLastSenderReport:   6,\n\t\t\tDelay:              7,\n\t\t})\n\t}\n\n\treturn tooManyReports\n}\n\nfunc TestReceiverReportRoundTrip(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tReport    ReceiverReport\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tReport: ReceiverReport{\n\t\t\t\tSSRC: 1,\n\t\t\t\tReports: []ReceptionReport{\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:               2,\n\t\t\t\t\t\tFractionLost:       2,\n\t\t\t\t\t\tTotalLost:          3,\n\t\t\t\t\t\tLastSequenceNumber: 4,\n\t\t\t\t\t\tJitter:             5,\n\t\t\t\t\t\tLastSenderReport:   6,\n\t\t\t\t\t\tDelay:              7,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tProfileExtensions: []byte{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"also valid\",\n\t\t\tReport: ReceiverReport{\n\t\t\t\tSSRC: 2,\n\t\t\t\tReports: []ReceptionReport{\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:               999,\n\t\t\t\t\t\tFractionLost:       30,\n\t\t\t\t\t\tTotalLost:          12345,\n\t\t\t\t\t\tLastSequenceNumber: 99,\n\t\t\t\t\t\tJitter:             22,\n\t\t\t\t\t\tLastSenderReport:   92,\n\t\t\t\t\t\tDelay:              46,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tProfileExtensions: []byte{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"totallost overflow\",\n\t\t\tReport: ReceiverReport{\n\t\t\t\tSSRC: 1,\n\t\t\t\tReports: []ReceptionReport{{\n\t\t\t\t\tTotalLost: 1 << 25,\n\t\t\t\t}},\n\t\t\t},\n\t\t\tWantError: errInvalidTotalLost,\n\t\t},\n\t\t{\n\t\t\tName: \"count overflow\",\n\t\t\tReport: ReceiverReport{\n\t\t\t\tSSRC:    1,\n\t\t\t\tReports: tooManyReports(),\n\t\t\t},\n\t\t\tWantError: errTooManyReports,\n\t\t},\n\t} {\n\t\tdata, err := test.Report.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar decoded ReceiverReport\n\t\tassert.NoErrorf(t, decoded.Unmarshal(data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Report, decoded, \"%s rr round trip mismatch\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "reception_report.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport \"encoding/binary\"\n\n// A ReceptionReport block conveys statistics on the reception of RTP packets\n// from a single synchronization source.\ntype ReceptionReport struct {\n\t// The SSRC identifier of the source to which the information in this\n\t// reception report block pertains.\n\tSSRC uint32\n\t// The fraction of RTP data packets from source SSRC lost since the\n\t// previous SR or RR packet was sent, expressed as a fixed point\n\t// number with the binary point at the left edge of the field.\n\tFractionLost uint8\n\t// The total number of RTP data packets from source SSRC that have\n\t// been lost since the beginning of reception.\n\tTotalLost uint32\n\t// The low 16 bits contain the highest sequence number received in an\n\t// RTP data packet from source SSRC, and the most significant 16\n\t// bits extend that sequence number with the corresponding count of\n\t// sequence number cycles.\n\tLastSequenceNumber uint32\n\t// An estimate of the statistical variance of the RTP data packet\n\t// interarrival time, measured in timestamp units and expressed as an\n\t// unsigned integer.\n\tJitter uint32\n\t// The middle 32 bits out of 64 in the NTP timestamp received as part of\n\t// the most recent RTCP sender report (SR) packet from source SSRC. If no\n\t// SR has been received yet, the field is set to zero.\n\tLastSenderReport uint32\n\t// The delay, expressed in units of 1/65536 seconds, between receiving the\n\t// last SR packet from source SSRC and sending this reception report block.\n\t// If no SR packet has been received yet from SSRC, the field is set to zero.\n\tDelay uint32\n}\n\nconst (\n\treceptionReportLength = 24\n\tfractionLostOffset    = 4\n\ttotalLostOffset       = 5\n\tlastSeqOffset         = 8\n\tjitterOffset          = 12\n\tlastSROffset          = 16\n\tdelayOffset           = 20\n)\n\n// Marshal encodes the ReceptionReport in binary.\nfunc (r ReceptionReport) Marshal() ([]byte, error) {\n\t/*\n\t *  0                   1                   2                   3\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 * |                              SSRC                             |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * | fraction lost |       cumulative number of packets lost       |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |           extended highest sequence number received           |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |                      interarrival jitter                      |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |                         last SR (LSR)                         |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |                   delay since last SR (DLSR)                  |\n\t * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t */\n\n\trawPacket := make([]byte, receptionReportLength)\n\n\tbinary.BigEndian.PutUint32(rawPacket, r.SSRC)\n\n\trawPacket[fractionLostOffset] = r.FractionLost\n\n\t// pack TotalLost into 24 bits\n\tif r.TotalLost >= (1 << 25) {\n\t\treturn nil, errInvalidTotalLost\n\t}\n\ttlBytes := rawPacket[totalLostOffset:]\n\ttlBytes[0] = byte(r.TotalLost >> 16) //nolint:gosec // rawPacket is created with length receptionReportLength (24)\n\ttlBytes[1] = byte(r.TotalLost >> 8)  //nolint:gosec // rawPacket is created with length receptionReportLength (24)\n\ttlBytes[2] = byte(r.TotalLost)       //nolint:gosec // rawPacket is created with length receptionReportLength (24)\n\n\tbinary.BigEndian.PutUint32(rawPacket[lastSeqOffset:], r.LastSequenceNumber)\n\tbinary.BigEndian.PutUint32(rawPacket[jitterOffset:], r.Jitter)\n\tbinary.BigEndian.PutUint32(rawPacket[lastSROffset:], r.LastSenderReport)\n\tbinary.BigEndian.PutUint32(rawPacket[delayOffset:], r.Delay)\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the ReceptionReport from binary.\nfunc (r *ReceptionReport) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) < receptionReportLength {\n\t\treturn errPacketTooShort\n\t}\n\n\t/*\n\t *  0                   1                   2                   3\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 * |                              SSRC                             |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * | fraction lost |       cumulative number of packets lost       |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |           extended highest sequence number received           |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |                      interarrival jitter                      |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |                         last SR (LSR)                         |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |                   delay since last SR (DLSR)                  |\n\t * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t */\n\n\tr.SSRC = binary.BigEndian.Uint32(rawPacket)\n\tr.FractionLost = rawPacket[fractionLostOffset]\n\n\ttlBytes := rawPacket[totalLostOffset:]\n\tr.TotalLost = uint32(tlBytes[2]) | uint32(tlBytes[1])<<8 | uint32(tlBytes[0])<<16\n\n\tr.LastSequenceNumber = binary.BigEndian.Uint32(rawPacket[lastSeqOffset:])\n\tr.Jitter = binary.BigEndian.Uint32(rawPacket[jitterOffset:])\n\tr.LastSenderReport = binary.BigEndian.Uint32(rawPacket[lastSROffset:])\n\tr.Delay = binary.BigEndian.Uint32(rawPacket[delayOffset:])\n\n\treturn nil\n}\n\nfunc (r *ReceptionReport) len() int {\n\treturn receptionReportLength\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"github>pion/renovate-config\"\n  ]\n}\n"
  },
  {
    "path": "rfc8888.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"strings\"\n)\n\n// https://www.rfc-editor.org/rfc/rfc8888.html#name-rtcp-congestion-control-fee\n//  0                   1                   2                   3\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=11  |   PT = 205    |          length               |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                 SSRC of RTCP packet sender                    |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                   SSRC of 1st RTP Stream                      |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |          begin_seq            |          num_reports          |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |R|ECN|  Arrival time offset    | ...                           .\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// .                                                               .\n// .                                                               .\n// .                                                               .\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                   SSRC of nth RTP Stream                      |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |          begin_seq            |          num_reports          |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |R|ECN|  Arrival time offset    | ...                           |\n// .                                                               .\n// .                                                               .\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |                 Report Timestamp (32 bits)                    |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\nvar (\n\terrReportBlockLength   = errors.New(\"feedback report blocks must be at least 8 bytes\")\n\terrIncorrectNumReports = errors.New(\"feedback report block contains less reports than num_reports\")\n\terrMetricBlockLength   = errors.New(\"feedback report metric blocks must be exactly 2 bytes\")\n)\n\n// ECN represents the two ECN bits.\ntype ECN uint8\n\nconst (\n\t//nolint:misspell\n\t// ECNNonECT signals Non ECN-Capable Transport, Non-ECT.\n\tECNNonECT ECN = iota // 00\n\n\t//nolint:misspell\n\t// ECNECT1 signals ECN Capable Transport, ECT(0).\n\tECNECT1 // 01\n\n\t//nolint:misspell\n\t// ECNECT0 signals ECN Capable Transport, ECT(1).\n\tECNECT0 // 10\n\n\t// ECNCE signals ECN Congestion Encountered, CE.\n\tECNCE // 11\n)\n\nfunc (e ECN) String() string {\n\tswitch e {\n\tcase ECNNonECT:\n\t\t//nolint:misspell\n\t\treturn \"Non-ECT (00)\"\n\tcase ECNECT0:\n\t\t//nolint:misspell\n\t\treturn \"ECT(0) (01)\"\n\tcase ECNECT1:\n\t\t//nolint:misspell\n\t\treturn \"ECT(1) (10)\"\n\tcase ECNCE:\n\t\t//nolint:misspell\n\t\treturn \"CE (11)\"\n\t}\n\n\treturn \"invalid ECN value\"\n}\n\nconst (\n\treportTimestampLength = 4\n\treportBlockOffset     = 8\n)\n\n// CCFeedbackReport is a Congestion Control Feedback Report as defined in\n// https://www.rfc-editor.org/rfc/rfc8888.html#name-rtcp-congestion-control-fee\ntype CCFeedbackReport struct {\n\t// SSRC of sender\n\tSenderSSRC uint32\n\n\t// Report Blocks\n\tReportBlocks []CCFeedbackReportBlock\n\n\t// Basetime\n\tReportTimestamp uint32\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (b CCFeedbackReport) DestinationSSRC() []uint32 {\n\tssrcs := make([]uint32, len(b.ReportBlocks))\n\tfor i, block := range b.ReportBlocks {\n\t\tssrcs[i] = block.MediaSSRC\n\t}\n\n\treturn ssrcs\n}\n\n// Len returns the length of the report in bytes.\nfunc (b *CCFeedbackReport) Len() int {\n\treturn b.MarshalSize()\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (b *CCFeedbackReport) MarshalSize() int {\n\tn := 0\n\tfor _, block := range b.ReportBlocks {\n\t\tn += block.len()\n\t}\n\n\treturn reportBlockOffset + n + reportTimestampLength\n}\n\n// Header returns the Header associated with this packet.\nfunc (b *CCFeedbackReport) Header() Header {\n\treturn Header{\n\t\tPadding: false,\n\t\tCount:   FormatCCFB,\n\t\tType:    TypeTransportSpecificFeedback,\n\t\tLength:  uint16(b.MarshalSize()/4 - 1), //nolint:gosec // G115\n\t}\n}\n\n// Marshal encodes the Congestion Control Feedback Report in binary.\nfunc (b CCFeedbackReport) Marshal() ([]byte, error) {\n\theader := b.Header()\n\theaderBuf, err := header.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlength := 4 * (header.Length + 1)\n\tbuf := make([]byte, length)\n\tcopy(buf[:headerLength], headerBuf)\n\tbinary.BigEndian.PutUint32(buf[headerLength:], b.SenderSSRC)\n\toffset := reportBlockOffset\n\tfor _, block := range b.ReportBlocks {\n\t\tb, err := block.marshal()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcopy(buf[offset:], b)\n\t\toffset += block.len()\n\t}\n\n\tbinary.BigEndian.PutUint32(buf[offset:], b.ReportTimestamp)\n\n\treturn buf, nil\n}\n\nfunc (b CCFeedbackReport) String() string {\n\tvar out strings.Builder\n\tfmt.Fprintf(&out, \"CCFB:\\n\\tHeader %v\\n\", b.Header())\n\tfmt.Fprintf(&out, \"CCFB:\\n\\tSender SSRC %d\\n\", b.SenderSSRC)\n\tfmt.Fprintf(&out, \"\\tReport Timestamp %d\\n\", b.ReportTimestamp)\n\tout.WriteString(\"\\tFeedback Reports \\n\")\n\tfor _, report := range b.ReportBlocks {\n\t\tfmt.Fprintf(&out, \"%v \", report)\n\t}\n\tout.WriteString(\"\\n\")\n\n\treturn out.String()\n}\n\n// Unmarshal decodes the Congestion Control Feedback Report from binary.\nfunc (b *CCFeedbackReport) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) < headerLength+ssrcLength+reportTimestampLength {\n\t\treturn errPacketTooShort\n\t}\n\n\tvar h Header\n\tif err := h.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\tif h.Type != TypeTransportSpecificFeedback {\n\t\treturn errWrongType\n\t}\n\n\tb.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])\n\n\treportTimestampOffset := len(rawPacket) - reportTimestampLength\n\tb.ReportTimestamp = binary.BigEndian.Uint32(rawPacket[reportTimestampOffset:])\n\n\toffset := reportBlockOffset\n\tb.ReportBlocks = []CCFeedbackReportBlock{}\n\tfor offset < reportTimestampOffset {\n\t\tvar block CCFeedbackReportBlock\n\t\tif err := block.unmarshal(rawPacket[offset:]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tb.ReportBlocks = append(b.ReportBlocks, block)\n\t\toffset += block.len()\n\t}\n\n\treturn nil\n}\n\nconst (\n\tssrcOffset          = 0\n\tbeginSequenceOffset = 4\n\tnumReportsOffset    = 6\n\treportsOffset       = 8\n\n\tmaxMetricBlocks = 16384\n)\n\n// CCFeedbackReportBlock is a Feedback Report Block.\ntype CCFeedbackReportBlock struct {\n\t// SSRC of the RTP stream on which this block is reporting\n\tMediaSSRC     uint32\n\tBeginSequence uint16\n\tMetricBlocks  []CCFeedbackMetricBlock\n}\n\n// len returns the length of the report block in bytes.\nfunc (b *CCFeedbackReportBlock) len() int {\n\tn := len(b.MetricBlocks)\n\tif n%2 != 0 {\n\t\tn++\n\t}\n\n\treturn reportsOffset + 2*n\n}\n\nfunc (b CCFeedbackReportBlock) String() string {\n\tvar out strings.Builder\n\tfmt.Fprintf(&out, \"\\tReport Block Media SSRC %d\\n\", b.MediaSSRC)\n\tfmt.Fprintf(&out, \"\\tReport Begin Sequence Nr %d\\n\", b.BeginSequence)\n\tfmt.Fprintf(&out, \"\\tReport length %d\\n\\t\", len(b.MetricBlocks))\n\tfor i, block := range b.MetricBlocks {\n\t\t//nolint:gosec // G115\n\t\tfmt.Fprintf(&out, \"{nr: %d, rx: %v, ts: %v, ecn: %v} \",\n\t\t\tb.BeginSequence+uint16(i),\n\t\t\tblock.Received,\n\t\t\tblock.ArrivalTimeOffset,\n\t\t\tblock.ECN)\n\t}\n\tout.WriteString(\"\\n\")\n\n\treturn out.String()\n}\n\n// marshal encodes the Congestion Control Feedback Report Block in binary.\nfunc (b CCFeedbackReportBlock) marshal() ([]byte, error) {\n\tif len(b.MetricBlocks) > maxMetricBlocks {\n\t\treturn nil, errTooManyReports\n\t}\n\n\tbuf := make([]byte, b.len())\n\tbinary.BigEndian.PutUint32(buf[ssrcOffset:], b.MediaSSRC)\n\tbinary.BigEndian.PutUint16(buf[beginSequenceOffset:], b.BeginSequence)\n\n\tlength := uint16(len(b.MetricBlocks)) //nolint:gosec // G115\n\n\tbinary.BigEndian.PutUint16(buf[numReportsOffset:], length)\n\n\tfor i, block := range b.MetricBlocks {\n\t\tb, err := block.marshal()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcopy(buf[reportsOffset+i*2:], b)\n\t}\n\n\treturn buf, nil\n}\n\n// Unmarshal decodes the Congestion Control Feedback Report Block from binary.\nfunc (b *CCFeedbackReportBlock) unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) < reportsOffset {\n\t\treturn errReportBlockLength\n\t}\n\tb.MediaSSRC = binary.BigEndian.Uint32(rawPacket[:beginSequenceOffset])\n\tb.BeginSequence = binary.BigEndian.Uint16(rawPacket[beginSequenceOffset:numReportsOffset])\n\tnumReports := int(binary.BigEndian.Uint16(rawPacket[numReportsOffset:]))\n\tif numReports == 0 {\n\t\treturn nil\n\t}\n\n\tif numReports > math.MaxUint16 {\n\t\treturn errIncorrectNumReports\n\t}\n\n\tif len(rawPacket) < reportsOffset+numReports*2 {\n\t\treturn errIncorrectNumReports\n\t}\n\n\tb.MetricBlocks = make([]CCFeedbackMetricBlock, numReports)\n\tfor i := range numReports {\n\t\tvar mb CCFeedbackMetricBlock\n\t\toffset := reportsOffset + 2*i\n\t\tif err := mb.unmarshal(rawPacket[offset : offset+2]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tb.MetricBlocks[i] = mb\n\t}\n\n\treturn nil\n}\n\nconst (\n\tmetricBlockLength = 2\n)\n\n// CCFeedbackMetricBlock is a Feedback Metric Block.\ntype CCFeedbackMetricBlock struct {\n\tReceived bool\n\tECN      ECN\n\n\t// Offset in 1/1024 seconds before Report Timestamp\n\tArrivalTimeOffset uint16\n}\n\n// Marshal encodes the Congestion Control Feedback Metric Block in binary.\nfunc (b CCFeedbackMetricBlock) marshal() ([]byte, error) {\n\tbuf := make([]byte, 2)\n\tr := uint16(0)\n\tif b.Received {\n\t\tr = 1\n\t}\n\tdst, err := setNBitsOfUint16(0, 1, 0, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdst, err = setNBitsOfUint16(dst, 2, 1, uint16(b.ECN))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdst, err = setNBitsOfUint16(dst, 13, 3, b.ArrivalTimeOffset)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbinary.BigEndian.PutUint16(buf, dst)\n\n\treturn buf, nil\n}\n\n// Unmarshal decodes the Congestion Control Feedback Metric Block from binary.\nfunc (b *CCFeedbackMetricBlock) unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) != metricBlockLength {\n\t\treturn errMetricBlockLength\n\t}\n\tb.Received = rawPacket[0]&0x80 != 0\n\tif !b.Received {\n\t\tb.ECN = ECNNonECT\n\t\tb.ArrivalTimeOffset = 0\n\n\t\treturn nil\n\t}\n\tb.ECN = ECN(rawPacket[0] >> 5 & 0x03)\n\tb.ArrivalTimeOffset = binary.BigEndian.Uint16(rawPacket) & 0x1FFF\n\n\treturn nil\n}\n"
  },
  {
    "path": "rfc8888_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*CCFeedbackReport)(nil) // assert is a Packet\n\nfunc TestCCFeedbackMetricBlockUnmarshalMarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName string\n\t\tData []byte\n\t\tWant CCFeedbackMetricBlock\n\t}{\n\t\t{\n\t\t\tName: \"NotReceived\",\n\t\t\tData: []byte{0x00, 0x00},\n\t\t\tWant: CCFeedbackMetricBlock{\n\t\t\t\tReceived:          false,\n\t\t\t\tECN:               0,\n\t\t\t\tArrivalTimeOffset: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ReceivedNoOffset\",\n\t\t\tData: []byte{0x80, 0x00},\n\t\t\tWant: CCFeedbackMetricBlock{\n\t\t\t\tReceived:          true,\n\t\t\t\tECN:               0,\n\t\t\t\tArrivalTimeOffset: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ReceivedOffset\",\n\t\t\tData: []byte{0x9F, 0xFD},\n\t\t\tWant: CCFeedbackMetricBlock{\n\t\t\t\tReceived:          true,\n\t\t\t\tECN:               0,\n\t\t\t\tArrivalTimeOffset: 8189,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ReceivedOverRangeOffset\",\n\t\t\tData: []byte{0x9F, 0xFE},\n\t\t\tWant: CCFeedbackMetricBlock{\n\t\t\t\tReceived:          true,\n\t\t\t\tECN:               0,\n\t\t\t\tArrivalTimeOffset: 8190,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ReceivedAfterReportTimestamp\",\n\t\t\tData: []byte{0x9F, 0xFF},\n\t\t\tWant: CCFeedbackMetricBlock{\n\t\t\t\tReceived:          true,\n\t\t\t\tECN:               0,\n\t\t\t\tArrivalTimeOffset: 8191,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ReceivedECNCE\",\n\t\t\tData: []byte{0xFF, 0xF8},\n\t\t\tWant: CCFeedbackMetricBlock{\n\t\t\t\tReceived:          true,\n\t\t\t\tECN:               ECNCE,\n\t\t\t\tArrivalTimeOffset: 8184,\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"Unmarshal-%v\", test.Name), func(t *testing.T) {\n\t\t\tvar block CCFeedbackMetricBlock\n\t\t\terr := block.unmarshal(test.Data)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, test.Want, block)\n\t\t})\n\t\tt.Run(fmt.Sprintf(\"Marshal-%v\", test.Name), func(t *testing.T) {\n\t\t\tbuf, err := test.Want.marshal()\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, test.Data, buf)\n\t\t})\n\t}\n\n\tfor _, test := range []struct {\n\t\tName string\n\t\tData []byte\n\t\tWant CCFeedbackMetricBlock\n\t}{\n\t\t{\n\t\t\tName: \"NotReceivedECNCE\", // Not received must ignore 15 other bits\n\t\t\tData: []byte{0x62, 0x00},\n\t\t\tWant: CCFeedbackMetricBlock{\n\t\t\t\tReceived:          false,\n\t\t\t\tECN:               ECNNonECT,\n\t\t\t\tArrivalTimeOffset: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"NotReceivedECNECT1\", // Not received must ignore 15 other bits\n\t\t\tData: []byte{0x22, 0x00},\n\t\t\tWant: CCFeedbackMetricBlock{\n\t\t\t\tReceived:          false,\n\t\t\t\tECN:               ECNNonECT,\n\t\t\t\tArrivalTimeOffset: 0,\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"Unmarshal-%v\", test.Name), func(t *testing.T) {\n\t\t\tvar block CCFeedbackMetricBlock\n\t\t\terr := block.unmarshal(test.Data)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, test.Want, block)\n\t\t})\n\t}\n\n\tfor _, l := range []int{0, 1, 3} {\n\t\tt.Run(fmt.Sprintf(\"shortMetricBlock-%v\", l), func(t *testing.T) {\n\t\t\tvar block CCFeedbackMetricBlock\n\t\t\tdata := make([]byte, l)\n\t\t\terr := block.unmarshal(data)\n\t\t\tassert.Error(t, err)\n\t\t\tassert.ErrorIs(t, err, errMetricBlockLength)\n\t\t})\n\t}\n}\n\nfunc TestCCFeedbackReportBlockUnmarshalMarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName string\n\t\tData []byte\n\t\tWant CCFeedbackReportBlock\n\t}{\n\t\t{\n\t\t\tName: \"ZeroLengthBlock\",\n\t\t\tData: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},\n\t\t\tWant: CCFeedbackReportBlock{\n\t\t\t\tMediaSSRC:     0,\n\t\t\t\tBeginSequence: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ReceivedTwoOFFourBlocks\",\n\t\t\tData: []byte{\n\t\t\t\t0x00, 0x00, 0x00, 0x01, // SSRC\n\t\t\t\t0x00, 0x02, 0x00, 0x04, // begin_seq, num_reports\n\t\t\t\t0x9F, 0xFD, 0x9F, 0xFC, // reports[0], reports[1]\n\t\t\t\t0x00, 0x00, 0x00, 0x00, // reports[2], reports[3]\n\t\t\t},\n\t\t\tWant: CCFeedbackReportBlock{\n\t\t\t\tMediaSSRC:     1,\n\t\t\t\tBeginSequence: 2,\n\t\t\t\tMetricBlocks: []CCFeedbackMetricBlock{\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          true,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 8189,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          true,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 8188,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          false,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          false,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"ReceivedTwoOFThreeBlocksPadding\",\n\t\t\tData: []byte{\n\t\t\t\t0x00, 0x00, 0x00, 0x01, // SSRC\n\t\t\t\t0x00, 0x02, 0x00, 0x03, // begin_seq, num_reports\n\t\t\t\t0x9F, 0xFD, 0x9F, 0xFC, // reports[0], reports[1]\n\t\t\t\t0x00, 0x00, 0x00, 0x00, // reports[2], Padding\n\t\t\t},\n\t\t\tWant: CCFeedbackReportBlock{\n\t\t\t\tMediaSSRC:     1,\n\t\t\t\tBeginSequence: 2,\n\t\t\t\tMetricBlocks: []CCFeedbackMetricBlock{\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          true,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 8189,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          true,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 8188,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          false,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"WrapAroundSequenceNumber\",\n\t\t\tData: []byte{\n\t\t\t\t0x00, 0x00, 0x00, 0x01, // SSRC\n\t\t\t\t0xff, 0xfe, 0x00, 0x04, // begin_seq, num_reports\n\t\t\t\t0x9F, 0xFD, 0x9F, 0xFC, // reports[0], reports[1]\n\t\t\t\t0x00, 0x00, 0x00, 0x00, // reports[2], reports[3]\n\t\t\t},\n\t\t\tWant: CCFeedbackReportBlock{\n\t\t\t\tMediaSSRC:     1,\n\t\t\t\tBeginSequence: 65534,\n\t\t\t\tMetricBlocks: []CCFeedbackMetricBlock{\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          true,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 8189,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          true,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 8188,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          false,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tReceived:          false,\n\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\tArrivalTimeOffset: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"Unmarshal-%v\", test.Name), func(t *testing.T) {\n\t\t\tvar block CCFeedbackReportBlock\n\t\t\terr := block.unmarshal(test.Data)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, test.Want, block)\n\t\t})\n\t\tt.Run(fmt.Sprintf(\"Marshal-%v\", test.Name), func(t *testing.T) {\n\t\t\tbuf, err := test.Want.marshal()\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, test.Data, buf)\n\t\t})\n\t}\n\n\tt.Run(\"MarshalTooManyMetricBlocks\", func(t *testing.T) {\n\t\tblock := CCFeedbackReportBlock{\n\t\t\tMediaSSRC:     0,\n\t\t\tBeginSequence: 0,\n\t\t\tMetricBlocks:  make([]CCFeedbackMetricBlock, 16385),\n\t\t}\n\t\t_, err := block.marshal()\n\t\tassert.Error(t, err)\n\t\tassert.ErrorIs(t, err, errTooManyReports)\n\t})\n\n\tt.Run(\"emptyRawPacket\", func(t *testing.T) {\n\t\tvar block CCFeedbackReportBlock\n\t\tdata := []byte{}\n\t\terr := block.unmarshal(data)\n\t\tassert.Error(t, err)\n\t\tassert.ErrorIs(t, err, errReportBlockLength)\n\t})\n\n\tt.Run(\"shortRawPacket\", func(t *testing.T) {\n\t\tvar block CCFeedbackReportBlock\n\t\tdata := []byte{\n\t\t\t0x00, 0x00, 0x00, 0x01, // SSRC\n\t\t\t0x00, 0x02, // begin_seq\n\t\t}\n\t\terr := block.unmarshal(data)\n\t\tassert.Error(t, err)\n\t\tassert.ErrorIs(t, err, errReportBlockLength)\n\t})\n\n\tt.Run(\"incorrectNumReports\", func(t *testing.T) {\n\t\tvar block CCFeedbackReportBlock\n\t\tdata := []byte{\n\t\t\t0x00, 0x00, 0x00, 0x01, // SSRC\n\t\t\t0x00, 0x02, 0x00, 0x05, // begin_seq, num_reports\n\t\t\t0x9F, 0xFD, 0x9F, 0xFC, // reports[0], reports[1]\n\t\t\t0x00, 0x00, 0x00, 0x00, // reports[2], reports[3]\n\t\t}\n\t\terr := block.unmarshal(data)\n\t\tassert.Error(t, err)\n\t\tassert.ErrorIs(t, err, errIncorrectNumReports)\n\t})\n\n\tt.Run(\"overflowNumReports\", func(t *testing.T) {\n\t\tvar block CCFeedbackReportBlock\n\t\tdata := append([]byte{\n\t\t\t0, 0, 0, 0, // SSRC\n\t\t\t0, 0, 0x7F, 0xFB, // begin_seq, num_reports\n\t\t}, bytes.Repeat([]byte{0, 0}, 0x7FFF)...)\n\t\terr := block.unmarshal(data)\n\t\tassert.NoError(t, err)\n\t})\n}\n\nfunc TestCCFeedbackReportUnmarshalMarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName string\n\t\tData []byte\n\t\tWant CCFeedbackReport\n\t}{\n\t\t{\n\t\t\tName: \"EmtpyReport\",\n\t\t\tData: []byte{\n\t\t\t\t0x8B, 0xCD, 0x00, 0x02, // V=2, P=0, FMT=11, PT=205, Length=2\n\t\t\t\t0x00, 0x00, 0x00, 0x01, // Sender SSRC=1\n\n\t\t\t\t0x00, 0x00, 0x00, 0x01, // Report Timestamp=1\n\t\t\t},\n\t\t\tWant: CCFeedbackReport{\n\t\t\t\tSenderSSRC:      1,\n\t\t\t\tReportBlocks:    []CCFeedbackReportBlock{},\n\t\t\t\tReportTimestamp: 1,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"Report\",\n\t\t\tData: []byte{\n\t\t\t\t0x8B, 0xCD, 0x00, 0x0A, // V=2, P=0, FMT=11, PT=205, Length=10\n\t\t\t\t0x00, 0x00, 0x00, 0x01, // Sender SSRC=1\n\n\t\t\t\t0x00, 0x00, 0x00, 0x01, // SSRC=1\n\t\t\t\t0x00, 0x02, 0x00, 0x04, // begin_seq, num_reports\n\t\t\t\t0x9F, 0xFD, 0x9F, 0xFC, // reports[0], reports[1]\n\t\t\t\t0x00, 0x00, 0x00, 0x00, // reports[2], reports[3]\n\n\t\t\t\t0x00, 0x00, 0x00, 0x02, // Media SSRC=2\n\t\t\t\t0x00, 0x02, 0x00, 0x03, // begin_seq=2, num_reports=3\n\t\t\t\t0x9F, 0xFD, 0x9F, 0xFC, // reports[0], reports[1]\n\t\t\t\t0x00, 0x00, 0x00, 0x00, // reports[2], Padding\n\n\t\t\t\t0x00, 0x00, 0x00, 0x01,\n\t\t\t},\n\t\t\tWant: CCFeedbackReport{\n\t\t\t\tSenderSSRC: 1,\n\t\t\t\tReportBlocks: []CCFeedbackReportBlock{\n\t\t\t\t\t{\n\t\t\t\t\t\tMediaSSRC:     1,\n\t\t\t\t\t\tBeginSequence: 2,\n\t\t\t\t\t\tMetricBlocks: []CCFeedbackMetricBlock{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tReceived:          true,\n\t\t\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\t\t\tArrivalTimeOffset: 8189,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tReceived:          true,\n\t\t\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\t\t\tArrivalTimeOffset: 8188,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tReceived:          false,\n\t\t\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\t\t\tArrivalTimeOffset: 0,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tReceived:          false,\n\t\t\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\t\t\tArrivalTimeOffset: 0,\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{\n\t\t\t\t\t\tMediaSSRC:     2,\n\t\t\t\t\t\tBeginSequence: 2,\n\t\t\t\t\t\tMetricBlocks: []CCFeedbackMetricBlock{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tReceived:          true,\n\t\t\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\t\t\tArrivalTimeOffset: 8189,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tReceived:          true,\n\t\t\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\t\t\tArrivalTimeOffset: 8188,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tReceived:          false,\n\t\t\t\t\t\t\t\tECN:               0,\n\t\t\t\t\t\t\t\tArrivalTimeOffset: 0,\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\tReportTimestamp: 1,\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(fmt.Sprintf(\"Unmarshal-%v\", test.Name), func(t *testing.T) {\n\t\t\tpkts, err := Unmarshal(test.Data)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Len(t, pkts, 1)\n\n\t\t\tvar ok bool\n\t\t\tvar report *CCFeedbackReport\n\t\t\treport, ok = pkts[0].(*CCFeedbackReport)\n\t\t\tassert.True(t, ok)\n\n\t\t\tassert.Equal(t, test.Want, *report)\n\t\t})\n\t\tt.Run(fmt.Sprintf(\"Marshal-%v\", test.Name), func(t *testing.T) {\n\t\t\tbuf, err := test.Want.Marshal()\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, test.Data, buf)\n\t\t})\n\t}\n}\n\nfunc TestCCFeedbackOverflow(t *testing.T) {\n\tp := &CCFeedbackReport{}\n\terr := p.Unmarshal(append([]byte{\n\t\t// Header\n\t\t0b10000000, // V = 2\n\t\t205,        // h.Type = TypeTransportSpecificFeedback\n\t\t0, 0,       // h.Length (unused)\n\t\t// SSRC\n\t\t0, 0, 0, 0,\n\t\t// CCFeedbackReportBlock\n\t\t0, 0, 0, 0, 0, 0,\n\t\t0x7F, 0xFB, // numReportsField\n\t}, bytes.Repeat([]byte{0, 0}, 0x7FFF)...))\n\tassert.ErrorIs(t, err, errReportBlockLength)\n}\n"
  },
  {
    "path": "sender_report.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"strings\"\n)\n\n// A SenderReport (SR) packet provides reception quality feedback for an RTP stream.\ntype SenderReport struct {\n\t// The synchronization source identifier for the originator of this SR packet.\n\tSSRC uint32\n\t// The wallclock time when this report was sent so that it may be used in\n\t// combination with timestamps returned in reception reports from other\n\t// receivers to measure round-trip propagation to those receivers.\n\tNTPTime uint64\n\t// Corresponds to the same time as the NTP timestamp (above), but in\n\t// the same units and with the same random offset as the RTP\n\t// timestamps in data packets. This correspondence may be used for\n\t// intra- and inter-media synchronization for sources whose NTP\n\t// timestamps are synchronized, and may be used by media-independent\n\t// receivers to estimate the nominal RTP clock frequency.\n\tRTPTime uint32\n\t// The total number of RTP data packets transmitted by the sender\n\t// since starting transmission up until the time this SR packet was\n\t// generated.\n\tPacketCount uint32\n\t// The total number of payload octets (i.e., not including header or\n\t// padding) transmitted in RTP data packets by the sender since\n\t// starting transmission up until the time this SR packet was\n\t// generated.\n\tOctetCount uint32\n\t// Zero or more reception report blocks depending on the number of other\n\t// sources heard by this sender since the last report. Each reception report\n\t// block conveys statistics on the reception of RTP packets from a\n\t// single synchronization source.\n\tReports []ReceptionReport\n\t// ProfileExtensions contains additional, payload-specific information that needs to\n\t// be reported regularly about the sender.\n\tProfileExtensions []byte\n}\n\nconst (\n\tsrHeaderLength      = 24\n\tsrSSRCOffset        = 0\n\tsrNTPOffset         = srSSRCOffset + ssrcLength\n\tntpTimeLength       = 8\n\tsrRTPOffset         = srNTPOffset + ntpTimeLength\n\trtpTimeLength       = 4\n\tsrPacketCountOffset = srRTPOffset + rtpTimeLength\n\tsrPacketCountLength = 4\n\tsrOctetCountOffset  = srPacketCountOffset + srPacketCountLength\n\tsrOctetCountLength  = 4\n\tsrReportOffset      = srOctetCountOffset + srOctetCountLength\n)\n\n// Marshal encodes the SenderReport in binary.\nfunc (r SenderReport) Marshal() ([]byte, error) {\n\t/*\n\t *         0                   1                   2                   3\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 * header |V=2|P|    RC   |   PT=SR=200   |             length            |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                         SSRC of sender                        |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * sender |              NTP timestamp, most significant word             |\n\t * info   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |             NTP timestamp, least significant word             |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                         RTP timestamp                         |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                     sender's packet count                     |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                      sender's octet count                     |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * report |                 SSRC_1 (SSRC of first source)                 |\n\t * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *   1    | fraction lost |       cumulative number of packets lost       |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |           extended highest sequence number received           |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                      interarrival jitter                      |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                         last SR (LSR)                         |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                   delay since last SR (DLSR)                  |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * report |                 SSRC_2 (SSRC of second source)                |\n\t * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *   2    :                               ...                             :\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t *        |                  profile-specific extensions                  |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\n\trawPacket := make([]byte, r.MarshalSize())\n\tpacketBody := rawPacket[headerLength:]\n\n\tbinary.BigEndian.PutUint32(packetBody[srSSRCOffset:], r.SSRC)\n\tbinary.BigEndian.PutUint64(packetBody[srNTPOffset:], r.NTPTime)\n\tbinary.BigEndian.PutUint32(packetBody[srRTPOffset:], r.RTPTime)\n\tbinary.BigEndian.PutUint32(packetBody[srPacketCountOffset:], r.PacketCount)\n\tbinary.BigEndian.PutUint32(packetBody[srOctetCountOffset:], r.OctetCount)\n\n\toffset := srHeaderLength\n\tfor _, rp := range r.Reports {\n\t\tdata, err := rp.Marshal()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcopy(packetBody[offset:], data)\n\t\toffset += receptionReportLength\n\t}\n\n\tif len(r.Reports) > countMax {\n\t\treturn nil, errTooManyReports\n\t}\n\n\tcopy(packetBody[offset:], r.ProfileExtensions)\n\n\thData, err := r.Header().Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcopy(rawPacket, hData)\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the SenderReport from binary.\nfunc (r *SenderReport) Unmarshal(rawPacket []byte) error {\n\t/*\n\t *         0                   1                   2                   3\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 * header |V=2|P|    RC   |   PT=SR=200   |             length            |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                         SSRC of sender                        |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * sender |              NTP timestamp, most significant word             |\n\t * info   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |             NTP timestamp, least significant word             |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                         RTP timestamp                         |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                     sender's packet count                     |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                      sender's octet count                     |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * report |                 SSRC_1 (SSRC of first source)                 |\n\t * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *   1    | fraction lost |       cumulative number of packets lost       |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |           extended highest sequence number received           |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                      interarrival jitter                      |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                         last SR (LSR)                         |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                   delay since last SR (DLSR)                  |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * report |                 SSRC_2 (SSRC of second source)                |\n\t * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *   2    :                               ...                             :\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t *        |                  profile-specific extensions                  |\n\t *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\n\tif len(rawPacket) < (headerLength + srHeaderLength) {\n\t\treturn errPacketTooShort\n\t}\n\n\tvar header Header\n\tif err := header.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\n\tif header.Type != TypeSenderReport {\n\t\treturn errWrongType\n\t}\n\n\tpacketBody := rawPacket[headerLength:]\n\n\tr.SSRC = binary.BigEndian.Uint32(packetBody[srSSRCOffset:])\n\tr.NTPTime = binary.BigEndian.Uint64(packetBody[srNTPOffset:])\n\tr.RTPTime = binary.BigEndian.Uint32(packetBody[srRTPOffset:])\n\tr.PacketCount = binary.BigEndian.Uint32(packetBody[srPacketCountOffset:])\n\tr.OctetCount = binary.BigEndian.Uint32(packetBody[srOctetCountOffset:])\n\n\toffset := srReportOffset\n\tfor i := 0; i < int(header.Count); i++ {\n\t\trrEnd := offset + receptionReportLength\n\t\tif rrEnd > len(packetBody) {\n\t\t\treturn errPacketTooShort\n\t\t}\n\t\trrBody := packetBody[offset : offset+receptionReportLength]\n\t\toffset = rrEnd\n\n\t\tvar rr ReceptionReport\n\t\tif err := rr.Unmarshal(rrBody); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Reports = append(r.Reports, rr)\n\t}\n\n\tif offset < len(packetBody) {\n\t\tr.ProfileExtensions = packetBody[offset:]\n\t}\n\n\tif uint8(len(r.Reports)) != header.Count { //nolint:gosec // G115\n\t\treturn errInvalidHeader\n\t}\n\n\treturn nil\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (r *SenderReport) DestinationSSRC() []uint32 {\n\tout := make([]uint32, len(r.Reports)+1)\n\tfor i, v := range r.Reports {\n\t\tout[i] = v.SSRC\n\t}\n\tout[len(r.Reports)] = r.SSRC\n\n\treturn out\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (r *SenderReport) MarshalSize() int {\n\trepsLength := 0\n\tfor _, rep := range r.Reports {\n\t\trepsLength += rep.len()\n\t}\n\n\treturn headerLength + srHeaderLength + repsLength + len(r.ProfileExtensions)\n}\n\n// Header returns the Header associated with this packet.\nfunc (r *SenderReport) Header() Header {\n\treturn Header{\n\t\tCount:  uint8(len(r.Reports)), //nolint:gosec // G115\n\t\tType:   TypeSenderReport,\n\t\tLength: uint16((r.MarshalSize() / 4) - 1), //nolint:gosec // G115\n\t}\n}\n\nfunc (r SenderReport) String() string {\n\tvar out strings.Builder\n\tfmt.Fprintf(&out, \"SenderReport from %x\\n\", r.SSRC)\n\tfmt.Fprintf(&out, \"\\tNTPTime:\\t%d\\n\", r.NTPTime)\n\tfmt.Fprintf(&out, \"\\tRTPTIme:\\t%d\\n\", r.RTPTime)\n\tfmt.Fprintf(&out, \"\\tPacketCount:\\t%d\\n\", r.PacketCount)\n\tfmt.Fprintf(&out, \"\\tOctetCount:\\t%d\\n\", r.OctetCount)\n\n\tout.WriteString(\"\\tSSRC    \\tLost\\tLastSequence\\n\")\n\tfor _, i := range r.Reports {\n\t\tfmt.Fprintf(&out, \"\\t%x\\t%d/%d\\t%d\\n\", i.SSRC, i.FractionLost, i.TotalLost, i.LastSequenceNumber)\n\t}\n\tfmt.Fprintf(&out, \"\\tProfile Extension Data: %v\\n\", r.ProfileExtensions)\n\n\treturn out.String()\n}\n"
  },
  {
    "path": "sender_report_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"slices\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*SenderReport)(nil) // assert is a Packet\n\nfunc TestSenderReportUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      SenderReport\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName:      \"nil\",\n\t\t\tData:      nil,\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SR, len=7\n\t\t\t\t0x81, 0xc8, 0x0, 0x7,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// ntp=0xda8bd1fcdddda05a\n\t\t\t\t0xda, 0x8b, 0xd1, 0xfc,\n\t\t\t\t0xdd, 0xdd, 0xa0, 0x5a,\n\t\t\t\t// rtp=0xaaf4edd5\n\t\t\t\t0xaa, 0xf4, 0xed, 0xd5,\n\t\t\t\t// packetCount=1\n\t\t\t\t0x00, 0x00, 0x00, 0x01,\n\t\t\t\t// octetCount=2\n\t\t\t\t0x00, 0x00, 0x00, 0x02,\n\t\t\t\t// ssrc=0xbc5e9a40\n\t\t\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t// lastSeq=0x46e1\n\t\t\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t\t\t// jitter=273\n\t\t\t\t0x0, 0x0, 0x1, 0x11,\n\t\t\t\t// lsr=0x9f36432\n\t\t\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t\t\t// delay=150137\n\t\t\t\t0x0, 0x2, 0x4a, 0x79,\n\t\t\t},\n\t\t\tWant: SenderReport{\n\t\t\t\tSSRC:        0x902f9e2e,\n\t\t\t\tNTPTime:     0xda8bd1fcdddda05a,\n\t\t\t\tRTPTime:     0xaaf4edd5,\n\t\t\t\tPacketCount: 1,\n\t\t\t\tOctetCount:  2,\n\t\t\t\tReports: []ReceptionReport{{\n\t\t\t\t\tSSRC:               0xbc5e9a40,\n\t\t\t\t\tFractionLost:       0,\n\t\t\t\t\tTotalLost:          0,\n\t\t\t\t\tLastSequenceNumber: 0x46e1,\n\t\t\t\t\tJitter:             273,\n\t\t\t\t\tLastSenderReport:   0x9f36432,\n\t\t\t\t\tDelay:              150137,\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"wrong type\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, RR, len=7\n\t\t\t\t0x81, 0xc9, 0x0, 0x7,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// ntp=0xda8bd1fcdddda05a\n\t\t\t\t0xda, 0x8b, 0xd1, 0xfc,\n\t\t\t\t0xdd, 0xdd, 0xa0, 0x5a,\n\t\t\t\t// rtp=0xaaf4edd5\n\t\t\t\t0xaa, 0xf4, 0xed, 0xd5,\n\t\t\t\t// packetCount=1\n\t\t\t\t0x00, 0x00, 0x00, 0x01,\n\t\t\t\t// octetCount=2\n\t\t\t\t0x00, 0x00, 0x00, 0x02,\n\t\t\t\t// ssrc=0xbc5e9a40\n\t\t\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t// lastSeq=0x46e1\n\t\t\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t\t\t// jitter=273\n\t\t\t\t0x0, 0x0, 0x1, 0x11,\n\t\t\t\t// lsr=0x9f36432\n\t\t\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t\t\t// delay=150137\n\t\t\t\t0x0, 0x2, 0x4a, 0x79,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t\t{\n\t\t\tName: \"bad count in header\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SR, len=7\n\t\t\t\t0x82, 0xc8, 0x0, 0x7,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// ntp=0xda8bd1fcdddda05a\n\t\t\t\t0xda, 0x8b, 0xd1, 0xfc,\n\t\t\t\t0xdd, 0xdd, 0xa0, 0x5a,\n\t\t\t\t// rtp=0xaaf4edd5\n\t\t\t\t0xaa, 0xf4, 0xed, 0xd5,\n\t\t\t\t// packetCount=1\n\t\t\t\t0x00, 0x00, 0x00, 0x01,\n\t\t\t\t// octetCount=2\n\t\t\t\t0x00, 0x00, 0x00, 0x02,\n\t\t\t\t// ssrc=0xbc5e9a40\n\t\t\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t// lastSeq=0x46e1\n\t\t\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t\t\t// jitter=273\n\t\t\t\t0x0, 0x0, 0x1, 0x11,\n\t\t\t\t// lsr=0x9f36432\n\t\t\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t\t\t// delay=150137\n\t\t\t\t0x0, 0x2, 0x4a, 0x79,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"with extension\", // issue #447\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=0, SR, len=6\n\t\t\t\t0x80, 0xc8, 0x0, 0x6,\n\t\t\t\t// ssrc=0x2b7ec0c5\n\t\t\t\t0x2b, 0x7e, 0xc0, 0xc5,\n\t\t\t\t// ntp=0xe020a2a952a53fc0\n\t\t\t\t0xe0, 0x20, 0xa2, 0xa9,\n\t\t\t\t0x52, 0xa5, 0x3f, 0xc0,\n\t\t\t\t// rtp=0x2e48a552\n\t\t\t\t0x2e, 0x48, 0xa5, 0x52,\n\t\t\t\t// packetCount=70\n\t\t\t\t0x0, 0x0, 0x0, 0x46,\n\t\t\t\t// octetCount=4637\n\t\t\t\t0x0, 0x0, 0x12, 0x1d,\n\t\t\t\t// profile-specific extension\n\t\t\t\t0x81, 0xca, 0x0, 0x6,\n\t\t\t\t0x2b, 0x7e, 0xc0, 0xc5,\n\t\t\t\t0x1, 0x10, 0x4c, 0x63,\n\t\t\t\t0x49, 0x66, 0x7a, 0x58,\n\t\t\t\t0x6f, 0x6e, 0x44, 0x6f,\n\t\t\t\t0x72, 0x64, 0x53, 0x65,\n\t\t\t\t0x57, 0x36, 0x0, 0x0,\n\t\t\t},\n\t\t\tWant: SenderReport{\n\t\t\t\tSSRC:        0x2b7ec0c5,\n\t\t\t\tNTPTime:     0xe020a2a952a53fc0,\n\t\t\t\tRTPTime:     0x2e48a552,\n\t\t\t\tPacketCount: 70,\n\t\t\t\tOctetCount:  4637,\n\t\t\t\tProfileExtensions: []byte{\n\t\t\t\t\t0x81, 0xca, 0x0, 0x6,\n\t\t\t\t\t0x2b, 0x7e, 0xc0, 0xc5,\n\t\t\t\t\t0x1, 0x10, 0x4c, 0x63,\n\t\t\t\t\t0x49, 0x66, 0x7a, 0x58,\n\t\t\t\t\t0x6f, 0x6e, 0x44, 0x6f,\n\t\t\t\t\t0x72, 0x64, 0x53, 0x65,\n\t\t\t\t\t0x57, 0x36, 0x0, 0x0,\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t} {\n\t\tvar sr SenderReport\n\t\terr := sr.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q sr\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tassert.Equalf(t, test.Want, sr, \"Unmarshal %q sr\", test.Name)\n\n\t\tvar ssrcFound bool\n\t\tdstSsrc := sr.DestinationSSRC()\n\t\tif slices.Contains(dstSsrc, sr.SSRC) {\n\t\t\tssrcFound = true\n\t\t}\n\n\t\tassert.Truef(t, ssrcFound, \"Unmarshal %q sr: sr's DestinationSSRC should include it's SSRC field\", test.Name)\n\t}\n}\n\nfunc TestSenderReportRoundTrip(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tReport    SenderReport\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tReport: SenderReport{\n\t\t\t\tSSRC:        1,\n\t\t\t\tNTPTime:     999,\n\t\t\t\tRTPTime:     555,\n\t\t\t\tPacketCount: 32,\n\t\t\t\tOctetCount:  11,\n\t\t\t\tReports: []ReceptionReport{\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:               2,\n\t\t\t\t\t\tFractionLost:       2,\n\t\t\t\t\t\tTotalLost:          3,\n\t\t\t\t\t\tLastSequenceNumber: 4,\n\t\t\t\t\t\tJitter:             5,\n\t\t\t\t\t\tLastSenderReport:   6,\n\t\t\t\t\t\tDelay:              7,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"also valid\",\n\t\t\tReport: SenderReport{\n\t\t\t\tSSRC: 2,\n\t\t\t\tReports: []ReceptionReport{\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:               999,\n\t\t\t\t\t\tFractionLost:       30,\n\t\t\t\t\t\tTotalLost:          12345,\n\t\t\t\t\t\tLastSequenceNumber: 99,\n\t\t\t\t\t\tJitter:             22,\n\t\t\t\t\t\tLastSenderReport:   92,\n\t\t\t\t\t\tDelay:              46,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"extension\",\n\t\t\tReport: SenderReport{\n\t\t\t\tSSRC: 2,\n\t\t\t\tReports: []ReceptionReport{\n\t\t\t\t\t{\n\t\t\t\t\t\tSSRC:               999,\n\t\t\t\t\t\tFractionLost:       30,\n\t\t\t\t\t\tTotalLost:          12345,\n\t\t\t\t\t\tLastSequenceNumber: 99,\n\t\t\t\t\t\tJitter:             22,\n\t\t\t\t\t\tLastSenderReport:   92,\n\t\t\t\t\t\tDelay:              46,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tProfileExtensions: []byte{1, 2, 3, 4},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"count overflow\",\n\t\t\tReport: SenderReport{\n\t\t\t\tSSRC:    1,\n\t\t\t\tReports: tooManyReports(),\n\t\t\t},\n\t\t\tWantError: errTooManyReports,\n\t\t},\n\t} {\n\t\tdata, err := test.Report.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar decoded SenderReport\n\t\tassert.NoErrorf(t, decoded.Unmarshal(data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Report, decoded, \"%q sr round trip\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "slice_loss_indication.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"math\"\n)\n\n// SLIEntry represents a single entry to the SLI packet's\n// list of lost slices.\ntype SLIEntry struct {\n\t// ID of first lost slice\n\tFirst uint16\n\n\t// Number of lost slices\n\tNumber uint16\n\n\t// ID of related picture\n\tPicture uint8\n}\n\n// The SliceLossIndication packet informs the encoder about the loss of a picture slice.\ntype SliceLossIndication struct {\n\t// SSRC of sender\n\tSenderSSRC uint32\n\n\t// SSRC of the media source\n\tMediaSSRC uint32\n\n\tSLI []SLIEntry\n}\n\nconst (\n\tsliLength = 2\n\tsliOffset = 8\n)\n\n// Marshal encodes the SliceLossIndication in binary.\nfunc (p SliceLossIndication) Marshal() ([]byte, error) {\n\tif len(p.SLI)+sliLength > math.MaxUint8 {\n\t\treturn nil, errTooManyReports\n\t}\n\n\trawPacket := make([]byte, sliOffset+(len(p.SLI)*4))\n\tbinary.BigEndian.PutUint32(rawPacket, p.SenderSSRC)\n\tbinary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC)\n\tfor i, s := range p.SLI {\n\t\tsli := ((uint32(s.First) & 0x1FFF) << 19) |\n\t\t\t((uint32(s.Number) & 0x1FFF) << 6) |\n\t\t\t(uint32(s.Picture) & 0x3F)\n\t\tbinary.BigEndian.PutUint32(rawPacket[sliOffset+(4*i):], sli)\n\t}\n\thData, err := p.Header().Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn append(hData, rawPacket...), nil\n}\n\n// Unmarshal decodes the SliceLossIndication from binary.\nfunc (p *SliceLossIndication) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) < (headerLength + ssrcLength) {\n\t\treturn errPacketTooShort\n\t}\n\n\tvar header Header\n\tif err := header.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\n\tif len(rawPacket) < (headerLength + int(4*header.Length)) {\n\t\treturn errPacketTooShort\n\t}\n\n\tif header.Type != TypeTransportSpecificFeedback || header.Count != FormatSLI {\n\t\treturn errWrongType\n\t}\n\n\tp.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])\n\tp.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])\n\tfor i := headerLength + sliOffset; i < (headerLength + int(header.Length*4)); i += 4 {\n\t\tsli := binary.BigEndian.Uint32(rawPacket[i:])\n\t\tp.SLI = append(p.SLI, SLIEntry{\n\t\t\tFirst:   uint16((sli >> 19) & 0x1FFF), //nolint:gosec // G115\n\t\t\tNumber:  uint16((sli >> 6) & 0x1FFF),  //nolint:gosec // G115\n\t\t\tPicture: uint8(sli & 0x3F),            //nolint:gosec // G115\n\t\t})\n\t}\n\n\treturn nil\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (p *SliceLossIndication) MarshalSize() int {\n\treturn headerLength + sliOffset + (len(p.SLI) * 4)\n}\n\n// Header returns the Header associated with this packet.\nfunc (p *SliceLossIndication) Header() Header {\n\treturn Header{\n\t\tCount:  FormatSLI,\n\t\tType:   TypeTransportSpecificFeedback,\n\t\tLength: uint16((p.MarshalSize() / 4) - 1), //nolint:gosec // G115\n\t}\n}\n\nfunc (p *SliceLossIndication) String() string {\n\treturn fmt.Sprintf(\"SliceLossIndication %x %x %+v\", p.SenderSSRC, p.MediaSSRC, p.SLI)\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (p *SliceLossIndication) DestinationSSRC() []uint32 {\n\treturn []uint32{p.MediaSSRC}\n}\n"
  },
  {
    "path": "slice_loss_indication_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*SliceLossIndication)(nil) // assert is a Packet\n\nfunc TestSliceLossIndicationUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      SliceLossIndication\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tData: []byte{\n\t\t\t\t// SliceLossIndication\n\t\t\t\t0x82, 0xcd, 0x0, 0x3,\n\t\t\t\t// sender=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// media=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// nack 0xAAAA, 0x5555\n\t\t\t\t0x55, 0x50, 0x00, 0x2C,\n\t\t\t},\n\t\t\tWant: SliceLossIndication{\n\t\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t\t\tSLI:        []SLIEntry{{0xaaa, 0, 0x2C}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"short report\",\n\t\t\tData: []byte{\n\t\t\t\t0x81, 0xcd, 0x0, 0x2,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// report ends early\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"wrong type\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SR, len=7\n\t\t\t\t0x81, 0xc8, 0x0, 0x7,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// ssrc=0xbc5e9a40\n\t\t\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t// lastSeq=0x46e1\n\t\t\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t\t\t// jitter=273\n\t\t\t\t0x0, 0x0, 0x1, 0x11,\n\t\t\t\t// lsr=0x9f36432\n\t\t\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t\t\t// delay=150137\n\t\t\t\t0x0, 0x2, 0x4a, 0x79,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t\t{\n\t\t\tName:      \"nil\",\n\t\t\tData:      nil,\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t} {\n\t\tvar sli SliceLossIndication\n\t\terr := sli.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tassert.Equalf(t, test.Want, sli, \"Unmarshal %q rr\", test.Name)\n\t}\n}\n\nfunc TestSliceLossIndicationRoundTrip(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tReport    SliceLossIndication\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tReport: SliceLossIndication{\n\t\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t\t\tSLI:        []SLIEntry{{1, 0xAA, 0x1F}, {1034, 0x05, 0x6}},\n\t\t\t},\n\t\t},\n\t} {\n\t\tdata, err := test.Report.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar decoded SliceLossIndication\n\t\tassert.NoErrorf(t, decoded.Unmarshal(data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Report, decoded, \"%q sli round trip mismatch\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "source_description.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"strings\"\n)\n\n// SDESType is the item type used in the RTCP SDES control packet.\ntype SDESType uint8\n\n// RTP SDES item types registered with IANA.\n// See: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-5\n// .\nconst (\n\tSDESEnd      SDESType = iota // end of SDES list                RFC 3550, 6.5\n\tSDESCNAME                    // canonical name                  RFC 3550, 6.5.1\n\tSDESName                     // user name                       RFC 3550, 6.5.2\n\tSDESEmail                    // user's electronic mail address  RFC 3550, 6.5.3\n\tSDESPhone                    // user's phone number             RFC 3550, 6.5.4\n\tSDESLocation                 // geographic user location        RFC 3550, 6.5.5\n\tSDESTool                     // name of application or tool     RFC 3550, 6.5.6\n\tSDESNote                     // notice about the source         RFC 3550, 6.5.7\n\tSDESPrivate                  // private extensions              RFC 3550, 6.5.8  (not implemented)\n)\n\n//nolint:cyclop\nfunc (s SDESType) String() string {\n\tswitch s {\n\tcase SDESEnd:\n\t\treturn \"END\"\n\tcase SDESCNAME:\n\t\treturn \"CNAME\"\n\tcase SDESName:\n\t\treturn \"NAME\"\n\tcase SDESEmail:\n\t\treturn \"EMAIL\"\n\tcase SDESPhone:\n\t\treturn \"PHONE\"\n\tcase SDESLocation:\n\t\treturn \"LOC\"\n\tcase SDESTool:\n\t\treturn \"TOOL\"\n\tcase SDESNote:\n\t\treturn \"NOTE\"\n\tcase SDESPrivate:\n\t\treturn \"PRIV\"\n\tdefault:\n\t\treturn string(s)\n\t}\n}\n\nconst (\n\tsdesSourceLen        = 4\n\tsdesTypeLen          = 1\n\tsdesTypeOffset       = 0\n\tsdesOctetCountLen    = 1\n\tsdesOctetCountOffset = 1\n\tsdesMaxOctetCount    = (1 << 8) - 1\n\tsdesTextOffset       = 2\n)\n\n// A SourceDescription (SDES) packet describes the sources in an RTP stream.\ntype SourceDescription struct {\n\tChunks []SourceDescriptionChunk\n}\n\n// NewCNAMESourceDescription creates a new SourceDescription with a single CNAME item.\nfunc NewCNAMESourceDescription(ssrc uint32, cname string) *SourceDescription {\n\treturn &SourceDescription{\n\t\tChunks: []SourceDescriptionChunk{{\n\t\t\tSource: ssrc,\n\t\t\tItems: []SourceDescriptionItem{{\n\t\t\t\tType: SDESCNAME,\n\t\t\t\tText: cname,\n\t\t\t}},\n\t\t}},\n\t}\n}\n\n// Marshal encodes the SourceDescription in binary.\nfunc (s SourceDescription) Marshal() ([]byte, error) {\n\t/*\n\t *         0                   1                   2                   3\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 * header |V=2|P|    SC   |  PT=SDES=202  |             length            |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * chunk  |                          SSRC/CSRC_1                          |\n\t *   1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                           SDES items                          |\n\t *        |                              ...                              |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * chunk  |                          SSRC/CSRC_2                          |\n\t *   2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                           SDES items                          |\n\t *        |                              ...                              |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t */\n\n\trawPacket := make([]byte, s.MarshalSize())\n\tpacketBody := rawPacket[headerLength:]\n\n\tchunkOffset := 0\n\tfor _, c := range s.Chunks {\n\t\tdata, err := c.Marshal()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcopy(packetBody[chunkOffset:], data)\n\t\tchunkOffset += len(data)\n\t}\n\n\tif len(s.Chunks) > countMax {\n\t\treturn nil, errTooManyChunks\n\t}\n\n\thData, err := s.Header().Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcopy(rawPacket, hData)\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the SourceDescription from binary.\nfunc (s *SourceDescription) Unmarshal(rawPacket []byte) error {\n\t/*\n\t *         0                   1                   2                   3\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 * header |V=2|P|    SC   |  PT=SDES=202  |             length            |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * chunk  |                          SSRC/CSRC_1                          |\n\t *   1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                           SDES items                          |\n\t *        |                              ...                              |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t * chunk  |                          SSRC/CSRC_2                          |\n\t *   2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *        |                           SDES items                          |\n\t *        |                              ...                              |\n\t *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t */\n\n\tvar header Header\n\tif err := header.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\n\tif header.Type != TypeSourceDescription {\n\t\treturn errWrongType\n\t}\n\n\tfor i := headerLength; i < len(rawPacket); {\n\t\tvar chunk SourceDescriptionChunk\n\t\tif err := chunk.Unmarshal(rawPacket[i:]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.Chunks = append(s.Chunks, chunk)\n\n\t\ti += chunk.len()\n\t}\n\n\tif len(s.Chunks) != int(header.Count) {\n\t\treturn errInvalidHeader\n\t}\n\n\treturn nil\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (s *SourceDescription) MarshalSize() int {\n\tchunksLength := 0\n\tfor _, c := range s.Chunks {\n\t\tchunksLength += c.len()\n\t}\n\n\treturn headerLength + chunksLength\n}\n\n// Header returns the Header associated with this packet.\nfunc (s *SourceDescription) Header() Header {\n\treturn Header{\n\t\tCount:  uint8(len(s.Chunks)), //nolint:gosec // G115\n\t\tType:   TypeSourceDescription,\n\t\tLength: uint16((s.MarshalSize() / 4) - 1), //nolint:gosec // G115\n\t}\n}\n\n// A SourceDescriptionChunk contains items describing a single RTP source.\ntype SourceDescriptionChunk struct {\n\t// The source (ssrc) or contributing source (csrc) identifier this packet describes\n\tSource uint32\n\tItems  []SourceDescriptionItem\n}\n\n// Marshal encodes the SourceDescriptionChunk in binary.\nfunc (s SourceDescriptionChunk) Marshal() ([]byte, error) {\n\t/*\n\t *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t *  |                          SSRC/CSRC_1                          |\n\t *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *  |                           SDES items                          |\n\t *  |                              ...                              |\n\t *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t */\n\n\trawPacket := make([]byte, sdesSourceLen)\n\tbinary.BigEndian.PutUint32(rawPacket, s.Source)\n\n\tfor _, it := range s.Items {\n\t\tdata, err := it.Marshal()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trawPacket = append(rawPacket, data...) //nolint:makezero\n\t}\n\n\t// The list of items in each chunk MUST be terminated by one or more null octets\n\trawPacket = append(rawPacket, uint8(SDESEnd)) //nolint:makezero\n\n\t// additional null octets MUST be included if needed to pad until the next 32-bit boundary\n\trawPacket = append(rawPacket, make([]byte, getPadding(len(rawPacket)))...) //nolint:makezero\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the SourceDescriptionChunk from binary.\nfunc (s *SourceDescriptionChunk) Unmarshal(rawPacket []byte) error {\n\t/*\n\t *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t *  |                          SSRC/CSRC_1                          |\n\t *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *  |                           SDES items                          |\n\t *  |                              ...                              |\n\t *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t */\n\n\tif len(rawPacket) < (sdesSourceLen + sdesTypeLen) {\n\t\treturn errPacketTooShort\n\t}\n\n\ts.Source = binary.BigEndian.Uint32(rawPacket)\n\n\tfor i := 4; i < len(rawPacket); {\n\t\tif pktType := SDESType(rawPacket[i]); pktType == SDESEnd {\n\t\t\treturn nil\n\t\t}\n\n\t\tvar it SourceDescriptionItem\n\t\tif err := it.Unmarshal(rawPacket[i:]); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.Items = append(s.Items, it)\n\t\ti += it.Len()\n\t}\n\n\treturn errPacketTooShort\n}\n\nfunc (s SourceDescriptionChunk) len() int {\n\tchunkLen := sdesSourceLen\n\tfor _, it := range s.Items {\n\t\tchunkLen += it.Len()\n\t}\n\tchunkLen += sdesTypeLen // for terminating null octet\n\n\t// align to 32-bit boundary\n\tchunkLen += getPadding(chunkLen)\n\n\treturn chunkLen\n}\n\n// A SourceDescriptionItem is a part of a SourceDescription that describes a stream.\ntype SourceDescriptionItem struct {\n\t// The type identifier for this item. eg, SDESCNAME for canonical name description.\n\t//\n\t// Type zero or SDESEnd is interpreted as the end of an item list and cannot be used.\n\tType SDESType\n\t// Text is a unicode text blob associated with the item. Its meaning varies based on the item's Type.\n\tText string\n}\n\n// Len returns the length of the SourceDescriptionItem when encoded as binary.\nfunc (s SourceDescriptionItem) Len() int {\n\t/*\n\t *   0                   1                   2                   3\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 *  |    CNAME=1    |     length    | user and domain name        ...\n\t *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\treturn sdesTypeLen + sdesOctetCountLen + len([]byte(s.Text))\n}\n\n// Marshal encodes the SourceDescriptionItem in binary.\nfunc (s SourceDescriptionItem) Marshal() ([]byte, error) {\n\t/*\n\t *   0                   1                   2                   3\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 *  |    CNAME=1    |     length    | user and domain name        ...\n\t *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\n\tif s.Type == SDESEnd {\n\t\treturn nil, errSDESMissingType\n\t}\n\n\trawPacket := make([]byte, sdesTypeLen+sdesOctetCountLen)\n\n\trawPacket[sdesTypeOffset] = uint8(s.Type) //nolint:gosec // rawPacket is created with length 2\n\n\ttxtBytes := []byte(s.Text)\n\toctetCount := len(txtBytes)\n\tif octetCount > sdesMaxOctetCount {\n\t\treturn nil, errSDESTextTooLong\n\t}\n\trawPacket[sdesOctetCountOffset] = uint8(octetCount) //nolint:gosec // rawPacket is created with length 2\n\n\trawPacket = append(rawPacket, txtBytes...) //nolint:makezero\n\n\treturn rawPacket, nil\n}\n\n// Unmarshal decodes the SourceDescriptionItem from binary.\nfunc (s *SourceDescriptionItem) Unmarshal(rawPacket []byte) error {\n\t/*\n\t *   0                   1                   2                   3\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 *  |    CNAME=1    |     length    | user and domain name        ...\n\t *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\n\tif len(rawPacket) < (sdesTypeLen + sdesOctetCountLen) {\n\t\treturn errPacketTooShort\n\t}\n\n\ts.Type = SDESType(rawPacket[sdesTypeOffset])\n\n\toctetCount := int(rawPacket[sdesOctetCountOffset])\n\tif sdesTextOffset+octetCount > len(rawPacket) {\n\t\treturn errPacketTooShort\n\t}\n\n\ttxtBytes := rawPacket[sdesTextOffset : sdesTextOffset+octetCount]\n\ts.Text = string(txtBytes)\n\n\treturn nil\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (s *SourceDescription) DestinationSSRC() []uint32 {\n\tout := make([]uint32, len(s.Chunks))\n\tfor i, v := range s.Chunks {\n\t\tout[i] = v.Source\n\t}\n\n\treturn out\n}\n\nfunc (s *SourceDescription) String() string {\n\tvar out strings.Builder\n\tout.WriteString(\"Source Description:\\n\")\n\tfor _, c := range s.Chunks {\n\t\tfmt.Fprintf(&out, \"\\t%x: %s\\n\", c.Source, c.Items)\n\t}\n\n\treturn out.String()\n}\n"
  },
  {
    "path": "source_description_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*SourceDescription)(nil) // assert is a Packet\n\nfunc TestSourceDescriptionUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      SourceDescription\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName:      \"nil\",\n\t\t\tData:      nil,\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"no chunks\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=8\n\t\t\t\t0x80, 0xca, 0x00, 0x04,\n\t\t\t},\n\t\t\tWant: SourceDescription{\n\t\t\t\tChunks: nil,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"missing type\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=8\n\t\t\t\t0x81, 0xca, 0x00, 0x08,\n\t\t\t\t// ssrc=0x00000000\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"bad cname length\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=10\n\t\t\t\t0x81, 0xca, 0x00, 0x0a,\n\t\t\t\t// ssrc=0x00000000\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// CNAME, len = 1\n\t\t\t\t0x01, 0x01,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"short cname\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=9\n\t\t\t\t0x81, 0xca, 0x00, 0x09,\n\t\t\t\t// ssrc=0x00000000\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// CNAME, Missing length\n\t\t\t\t0x01,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"no end\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=11\n\t\t\t\t0x81, 0xca, 0x00, 0x0b,\n\t\t\t\t// ssrc=0x00000000\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// CNAME, len=1, content=A\n\t\t\t\t0x01, 0x02, 0x41,\n\t\t\t\t// Missing END\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"bad octet count\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=10\n\t\t\t\t0x81, 0xca, 0x00, 0x0a,\n\t\t\t\t// ssrc=0x00000000\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t\t// CNAME, len=1\n\t\t\t\t0x01, 0x01,\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"zero item chunk\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=12\n\t\t\t\t0x81, 0xca, 0x00, 0x0c,\n\t\t\t\t// ssrc=0x01020304\n\t\t\t\t0x01, 0x02, 0x03, 0x04,\n\t\t\t\t// END + padding\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWant: SourceDescription{\n\t\t\t\tChunks: []SourceDescriptionChunk{{\n\t\t\t\t\tSource: 0x01020304,\n\t\t\t\t\tItems:  nil,\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"wrong type\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SR, len=12\n\t\t\t\t0x81, 0xc8, 0x00, 0x0c,\n\t\t\t\t// ssrc=0x01020304\n\t\t\t\t0x01, 0x02, 0x03, 0x04,\n\t\t\t\t// END + padding\n\t\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t\t{\n\t\t\tName: \"bad count in header\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=12\n\t\t\t\t0x81, 0xca, 0x00, 0x0c,\n\t\t\t},\n\t\t\tWantError: errInvalidHeader,\n\t\t},\n\t\t{\n\t\t\tName: \"empty string\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=12\n\t\t\t\t0x81, 0xca, 0x00, 0x0c,\n\t\t\t\t// ssrc=0x01020304\n\t\t\t\t0x01, 0x02, 0x03, 0x04,\n\t\t\t\t// CNAME, len=0\n\t\t\t\t0x01, 0x00,\n\t\t\t\t// END + padding\n\t\t\t\t0x00, 0x00,\n\t\t\t},\n\t\t\tWant: *NewCNAMESourceDescription(0x01020304, \"\"),\n\t\t},\n\t\t{\n\t\t\tName: \"two items\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SDES, len=16\n\t\t\t\t0x81, 0xca, 0x00, 0x10,\n\t\t\t\t// ssrc=0x10000000\n\t\t\t\t0x10, 0x00, 0x00, 0x00,\n\t\t\t\t// CNAME, len=1, content=A\n\t\t\t\t0x01, 0x01, 0x41,\n\t\t\t\t// PHONE, len=1, content=B\n\t\t\t\t0x04, 0x01, 0x42,\n\t\t\t\t// END + padding\n\t\t\t\t0x00, 0x00,\n\t\t\t},\n\t\t\tWant: SourceDescription{\n\t\t\t\tChunks: []SourceDescriptionChunk{\n\t\t\t\t\t{\n\t\t\t\t\t\tSource: 0x10000000,\n\t\t\t\t\t\tItems: []SourceDescriptionItem{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType: SDESCNAME,\n\t\t\t\t\t\t\t\tText: \"A\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType: SDESPhone,\n\t\t\t\t\t\t\t\tText: \"B\",\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\t{\n\t\t\tName: \"two chunks\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=2, SDES, len=24\n\t\t\t\t0x82, 0xca, 0x00, 0x18,\n\t\t\t\t// ssrc=0x01020304\n\t\t\t\t0x01, 0x02, 0x03, 0x04,\n\t\t\t\t// Chunk 1\n\t\t\t\t// CNAME, len=1, content=A\n\t\t\t\t0x01, 0x01, 0x41,\n\t\t\t\t// END\n\t\t\t\t0x00,\n\t\t\t\t// Chunk 2\n\t\t\t\t// SSRC 0x05060708\n\t\t\t\t0x05, 0x06, 0x07, 0x08,\n\t\t\t\t// CNAME, len=3, content=BCD\n\t\t\t\t0x01, 0x03, 0x42, 0x43, 0x44,\n\t\t\t\t// END\n\t\t\t\t0x00, 0x00, 0x00,\n\t\t\t},\n\t\t\tWant: SourceDescription{\n\t\t\t\tChunks: []SourceDescriptionChunk{\n\t\t\t\t\t{\n\t\t\t\t\t\tSource: 0x01020304,\n\t\t\t\t\t\tItems: []SourceDescriptionItem{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType: SDESCNAME,\n\t\t\t\t\t\t\t\tText: \"A\",\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{\n\t\t\t\t\t\tSource: 0x05060708,\n\t\t\t\t\t\tItems: []SourceDescriptionItem{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType: SDESCNAME,\n\t\t\t\t\t\t\t\tText: \"BCD\",\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} {\n\t\tvar sdes SourceDescription\n\t\terr := sdes.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Want, sdes, \"Unmarshal %q\", test.Name)\n\t}\n}\n\nfunc TestSourceDescriptionRoundTrip(t *testing.T) {\n\t// a slice with enough SourceDescriptionChunks to overflow an 5-bit int\n\tvar tooManyChunks []SourceDescriptionChunk\n\tvar tooLongText strings.Builder\n\n\tfor range 1 << 5 {\n\t\ttooManyChunks = append(tooManyChunks, SourceDescriptionChunk{})\n\t}\n\tfor range 1 << 8 {\n\t\ttooLongText.WriteString(\"x\")\n\t}\n\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tDesc      SourceDescription\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tDesc: SourceDescription{\n\t\t\t\tChunks: []SourceDescriptionChunk{\n\t\t\t\t\t{\n\t\t\t\t\t\tSource: 1,\n\t\t\t\t\t\tItems: []SourceDescriptionItem{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType: SDESCNAME,\n\t\t\t\t\t\t\t\tText: \"test@example.com\",\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{\n\t\t\t\t\t\tSource: 2,\n\t\t\t\t\t\tItems: []SourceDescriptionItem{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType: SDESNote,\n\t\t\t\t\t\t\t\tText: \"some note\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType: SDESNote,\n\t\t\t\t\t\t\t\tText: \"another note\",\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\t{\n\t\t\tName: \"item without type\",\n\t\t\tDesc: SourceDescription{\n\t\t\t\tChunks: []SourceDescriptionChunk{{\n\t\t\t\t\tSource: 1,\n\t\t\t\t\tItems: []SourceDescriptionItem{{\n\t\t\t\t\t\tText: \"test@example.com\",\n\t\t\t\t\t}},\n\t\t\t\t}},\n\t\t\t},\n\t\t\tWantError: errSDESMissingType,\n\t\t},\n\t\t{\n\t\t\tName: \"zero items\",\n\t\t\tDesc: SourceDescription{\n\t\t\t\tChunks: []SourceDescriptionChunk{{\n\t\t\t\t\tSource: 1,\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"email item\",\n\t\t\tDesc: SourceDescription{\n\t\t\t\tChunks: []SourceDescriptionChunk{{\n\t\t\t\t\tSource: 1,\n\t\t\t\t\tItems: []SourceDescriptionItem{{\n\t\t\t\t\t\tType: SDESEmail,\n\t\t\t\t\t\tText: \"test@example.com\",\n\t\t\t\t\t}},\n\t\t\t\t}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"empty text\",\n\t\t\tDesc: *NewCNAMESourceDescription(1, \"\"),\n\t\t},\n\t\t{\n\t\t\tName: \"text too long\",\n\t\t\tDesc: SourceDescription{\n\t\t\t\tChunks: []SourceDescriptionChunk{{\n\t\t\t\t\tItems: []SourceDescriptionItem{{\n\t\t\t\t\t\tType: SDESCNAME,\n\t\t\t\t\t\tText: tooLongText.String(),\n\t\t\t\t\t}},\n\t\t\t\t}},\n\t\t\t},\n\t\t\tWantError: errSDESTextTooLong,\n\t\t},\n\t\t{\n\t\t\tName: \"count overflow\",\n\t\t\tDesc: SourceDescription{\n\t\t\t\tChunks: tooManyChunks,\n\t\t\t},\n\t\t\tWantError: errTooManyChunks,\n\t\t},\n\t} {\n\t\tdata, err := test.Desc.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal %q\", test.Name)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar decoded SourceDescription\n\t\tassert.NoErrorf(t, decoded.Unmarshal(data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Desc, decoded, \"%s sdes round trip mismatch\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/0b954a73147600a3",
    "content": "go test fuzz v1\n[]byte(\"\\x8f\\xcd\\x00 0000000000000000A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/16c369bd58290097",
    "content": "go test fuzz v1\n[]byte(\"\\x8f\\xcd\\x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\")\n"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/5eaf215c68e1ddb3",
    "content": "go test fuzz v1\n[]byte(\"\\x81\\xcd\\x00\\x010000\")\n"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/60753346a105d3c3",
    "content": "go test fuzz v1\n[]byte(\"\\x8b\\xcd\\x00\\x150000000000\\x80 000000000000000000000000000000000000000000000000000000000000000000000000\")\n"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/6366fbb9980fa33a",
    "content": "go test fuzz v1\n[]byte(\"\\xa4\\xce\\x00\\x010000\")\n"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/e1a48af9f8e7db71",
    "content": "go test fuzz v1\n[]byte(\"\\xaf\\xcd\\x00\\a0000000000000000\\xf70A000000000\")\n"
  },
  {
    "path": "transport_layer_cc.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\n// Author: adwpc\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"strings\"\n)\n\n// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5\n// 0                   1                   2                   3\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=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// for packet status chunk.\nconst (\n\t// type of packet status chunk.\n\tTypeTCCRunLengthChunk    = 0\n\tTypeTCCStatusVectorChunk = 1\n\n\t// len of packet status chunk.\n\tpacketStatusChunkLength = 2\n)\n\n// type of packet status symbol and recv delta.\nconst (\n\t// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.1\n\tTypeTCCPacketNotReceived = uint16(iota)\n\tTypeTCCPacketReceivedSmallDelta\n\tTypeTCCPacketReceivedLargeDelta\n\t// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7\n\t// see Example 2: \"packet received, w/o recv delta\".\n\tTypeTCCPacketReceivedWithoutDelta\n)\n\n// for status vector chunk.\nconst (\n\t// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.4\n\tTypeTCCSymbolSizeOneBit = 0\n\tTypeTCCSymbolSizeTwoBit = 1\n\n\t// Notice: RFC is wrong: \"packet received\" (0) and \"packet not received\" (1)\n\t// if S == TypeTCCSymbolSizeOneBit, symbol list will be: TypeTCCPacketNotReceived TypeTCCPacketReceivedSmallDelta\n\t// if S == TypeTCCSymbolSizeTwoBit, symbol list will be same as above:\n\t//.\n)\n\nfunc numOfBitsOfSymbolSize() map[uint16]uint16 {\n\treturn map[uint16]uint16{\n\t\tTypeTCCSymbolSizeOneBit: 1,\n\t\tTypeTCCSymbolSizeTwoBit: 2,\n\t}\n}\n\nvar (\n\terrPacketStatusChunkLength = errors.New(\"packet status chunk must be 2 bytes\")\n\terrDeltaExceedLimit        = errors.New(\"delta exceed limit\")\n)\n\n// PacketStatusChunk has two kinds:\n// RunLengthChunk and StatusVectorChunk.\ntype PacketStatusChunk interface {\n\tMarshal() ([]byte, error)\n\tUnmarshal(rawPacket []byte) error\n}\n\n// RunLengthChunk T=TypeTCCRunLengthChunk\n// 0                   1\n// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |T| S |       Run Length        |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// .\ntype RunLengthChunk struct {\n\tPacketStatusChunk\n\n\t// T = TypeTCCRunLengthChunk\n\tType uint16\n\n\t// S: type of packet status\n\t// kind: TypeTCCPacketNotReceived or...\n\tPacketStatusSymbol uint16\n\n\t// RunLength: count of S\n\tRunLength uint16\n}\n\n// Marshal ..\nfunc (r RunLengthChunk) Marshal() ([]byte, error) {\n\tchunk := make([]byte, 2)\n\n\t// append 1 bit '0'\n\tdst, err := setNBitsOfUint16(0, 1, 0, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// append 2 bit PacketStatusSymbol\n\tdst, err = setNBitsOfUint16(dst, 2, 1, r.PacketStatusSymbol)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// append 13 bit RunLength\n\tdst, err = setNBitsOfUint16(dst, 13, 3, r.RunLength)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbinary.BigEndian.PutUint16(chunk, dst)\n\n\treturn chunk, nil\n}\n\n// Unmarshal ..\nfunc (r *RunLengthChunk) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) != packetStatusChunkLength {\n\t\treturn errPacketStatusChunkLength\n\t}\n\n\t// record type\n\tr.Type = TypeTCCRunLengthChunk\n\n\t// get PacketStatusSymbol\n\t// r.PacketStatusSymbol = uint16(rawPacket[0] >> 5 & 0x03)\n\tr.PacketStatusSymbol = getNBitsFromByte(rawPacket[0], 1, 2)\n\n\t// get RunLength\n\t// r.RunLength = uint16(rawPacket[0]&0x1F)*256 + uint16(rawPacket[1])\n\tr.RunLength = getNBitsFromByte(rawPacket[0], 3, 5)<<8 + uint16(rawPacket[1])\n\n\treturn nil\n}\n\n// StatusVectorChunk T=typeStatusVecotrChunk\n// 0                   1\n// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |T|S|       symbol list         |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// .\ntype StatusVectorChunk struct {\n\tPacketStatusChunk\n\t// T = TypeTCCRunLengthChunk\n\tType uint16\n\n\t// TypeTCCSymbolSizeOneBit or TypeTCCSymbolSizeTwoBit\n\tSymbolSize uint16\n\n\t// when SymbolSize = TypeTCCSymbolSizeOneBit, SymbolList is 14*1bit:\n\t// TypeTCCSymbolListPacketReceived or TypeTCCSymbolListPacketNotReceived\n\t// when SymbolSize = TypeTCCSymbolSizeTwoBit, SymbolList is 7*2bit:\n\t// TypeTCCPacketNotReceived TypeTCCPacketReceivedSmallDelta TypeTCCPacketReceivedLargeDelta or typePacketReserved\n\tSymbolList []uint16\n}\n\n// Marshal ..\nfunc (r StatusVectorChunk) Marshal() ([]byte, error) {\n\tchunk := make([]byte, 2)\n\n\t// set first bit '1'\n\tdst, err := setNBitsOfUint16(0, 1, 0, 1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// set second bit SymbolSize\n\tdst, err = setNBitsOfUint16(dst, 1, 1, r.SymbolSize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnumOfBits := numOfBitsOfSymbolSize()[r.SymbolSize]\n\t// append 14 bit SymbolList\n\tfor i, s := range r.SymbolList {\n\t\tindex := numOfBits*uint16(i) + 2 //nolint:gosec // G115\n\t\tdst, err = setNBitsOfUint16(dst, numOfBits, index, s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tbinary.BigEndian.PutUint16(chunk, dst)\n\t// set SymbolList(bit8-15)\n\t// chunk[1] = uint8(r.SymbolList) & 0x0f\n\treturn chunk, nil\n}\n\n// Unmarshal ..\nfunc (r *StatusVectorChunk) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) != packetStatusChunkLength {\n\t\treturn errPacketStatusChunkLength\n\t}\n\n\tr.Type = TypeTCCStatusVectorChunk\n\tr.SymbolSize = getNBitsFromByte(rawPacket[0], 1, 1)\n\n\tif r.SymbolSize == TypeTCCSymbolSizeOneBit {\n\t\tfor i := range uint16(6) {\n\t\t\tr.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[0], 2+i, 1))\n\t\t}\n\t\tfor i := range uint16(8) {\n\t\t\tr.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[1], i, 1))\n\t\t}\n\n\t\treturn nil\n\t}\n\tif r.SymbolSize == TypeTCCSymbolSizeTwoBit {\n\t\tfor i := range uint16(3) {\n\t\t\tr.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[0], 2+i*2, 2))\n\t\t}\n\t\tfor i := range uint16(4) {\n\t\t\tr.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[1], i*2, 2))\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tr.SymbolSize = getNBitsFromByte(rawPacket[0], 2, 6)<<8 + uint16(rawPacket[1])\n\n\treturn nil\n}\n\nconst (\n\t// TypeTCCDeltaScaleFactor https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.5\n\tTypeTCCDeltaScaleFactor = 250\n)\n\n// RecvDelta are represented as multiples of 250us\n// small delta is 1 byte: [0，63.75]ms = [0, 63750]us = [0, 255]*250us\n// big delta is 2 bytes: [-8192.0, 8191.75]ms = [-8192000, 8191750]us = [-32768, 32767]*250us\n// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.5\ntype RecvDelta struct {\n\tType uint16\n\t// us\n\tDelta int64\n}\n\n// Marshal ..\nfunc (r RecvDelta) Marshal() ([]byte, error) {\n\tdelta := r.Delta / TypeTCCDeltaScaleFactor\n\n\t// small delta\n\tif r.Type == TypeTCCPacketReceivedSmallDelta && delta >= 0 && delta <= math.MaxUint8 {\n\t\tdeltaChunk := make([]byte, 1)\n\t\tdeltaChunk[0] = byte(delta) //nolint:gosec // deltaChunk is created with length 1\n\n\t\treturn deltaChunk, nil\n\t}\n\n\t// big delta\n\tif r.Type == TypeTCCPacketReceivedLargeDelta && delta >= math.MinInt16 && delta <= math.MaxInt16 {\n\t\tdeltaChunk := make([]byte, 2)\n\t\tbinary.BigEndian.PutUint16(deltaChunk, uint16(delta)) //nolint:gosec  //delta is validated to fit in uint16\n\n\t\treturn deltaChunk, nil\n\t}\n\n\t// overflow\n\treturn nil, errDeltaExceedLimit\n}\n\n// Unmarshal ..\nfunc (r *RecvDelta) Unmarshal(rawPacket []byte) error {\n\tchunkLen := len(rawPacket)\n\n\t// must be 1 or 2 bytes\n\tif chunkLen != 1 && chunkLen != 2 {\n\t\treturn errDeltaExceedLimit\n\t}\n\n\tif chunkLen == 1 {\n\t\tr.Type = TypeTCCPacketReceivedSmallDelta\n\t\tr.Delta = TypeTCCDeltaScaleFactor * int64(rawPacket[0])\n\n\t\treturn nil\n\t}\n\n\tr.Type = TypeTCCPacketReceivedLargeDelta\n\tr.Delta = TypeTCCDeltaScaleFactor * int64(int16(binary.BigEndian.Uint16(rawPacket))) //nolint:gosec // G115\n\n\treturn nil\n}\n\nconst (\n\t// the offset after header.\n\tbaseSequenceNumberOffset = 8\n\tpacketStatusCountOffset  = 10\n\treferenceTimeOffset      = 12\n\tfbPktCountOffset         = 15\n\tpacketChunkOffset        = 16\n)\n\n// TransportLayerCC for sender-BWE\n// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5\ntype TransportLayerCC struct {\n\t// header\n\tHeader Header\n\n\t// SSRC of sender\n\tSenderSSRC uint32\n\n\t// SSRC of the media source\n\tMediaSSRC uint32\n\n\t// Transport wide sequence of rtp extension\n\tBaseSequenceNumber uint16\n\n\t// PacketStatusCount\n\tPacketStatusCount uint16\n\n\t// ReferenceTime\n\tReferenceTime uint32\n\n\t// FbPktCount\n\tFbPktCount uint8\n\n\t// PacketChunks\n\tPacketChunks []PacketStatusChunk\n\n\t// RecvDeltas\n\tRecvDeltas []*RecvDelta\n}\n\n// Header returns the Header associated with this packet.\n// func (t *TransportLayerCC) Header() Header {\n// return t.Header\n// return Header{\n// Padding: true,\n// Count:   FormatTCC,\n// Type:    TypeTCCTransportSpecificFeedback,\n// // https://tools.ietf.org/html/rfc4585#page-33\n// Length: uint16((t.len() / 4) - 1),\n// }\n// }\n\nfunc (t *TransportLayerCC) packetLen() uint16 {\n\t//nolint:gocognit,cyclop\n\tn := uint16(headerLength + packetChunkOffset + len(t.PacketChunks)*2) //nolint:gosec // G115\n\tfor _, d := range t.RecvDeltas {\n\t\tif d.Type == TypeTCCPacketReceivedSmallDelta {\n\t\t\tn++\n\t\t} else {\n\t\t\tn += 2\n\t\t}\n\t}\n\n\treturn n\n}\n\n// Len return total bytes with padding.\nfunc (t *TransportLayerCC) Len() uint16 {\n\treturn uint16(t.MarshalSize()) //nolint:gosec // G115\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (t *TransportLayerCC) MarshalSize() int {\n\tn := t.packetLen()\n\t// has padding\n\tif n%4 != 0 {\n\t\tn = (n/4 + 1) * 4\n\t}\n\n\treturn int(n)\n}\n\nfunc (t TransportLayerCC) String() string {\n\tvar out strings.Builder\n\tfmt.Fprintf(&out, \"TransportLayerCC:\\n\\tHeader %v\\n\", t.Header)\n\tfmt.Fprintf(&out, \"TransportLayerCC:\\n\\tSender Ssrc %d\\n\", t.SenderSSRC)\n\tfmt.Fprintf(&out, \"\\tMedia Ssrc %d\\n\", t.MediaSSRC)\n\tfmt.Fprintf(&out, \"\\tBase Sequence Number %d\\n\", t.BaseSequenceNumber)\n\tfmt.Fprintf(&out, \"\\tStatus Count %d\\n\", t.PacketStatusCount)\n\tfmt.Fprintf(&out, \"\\tReference Time %d\\n\", t.ReferenceTime)\n\tfmt.Fprintf(&out, \"\\tFeedback Packet Count %d\\n\", t.FbPktCount)\n\tout.WriteString(\"\\tPacketChunks \")\n\tfor _, chunk := range t.PacketChunks {\n\t\tfmt.Fprintf(&out, \"%+v \", chunk)\n\t}\n\tout.WriteString(\"\\n\\tRecvDeltas \")\n\tfor _, delta := range t.RecvDeltas {\n\t\tfmt.Fprintf(&out, \"%+v \", delta)\n\t}\n\tout.WriteString(\"\\n\")\n\n\treturn out.String()\n}\n\n// Marshal encodes the TransportLayerCC in binary.\nfunc (t TransportLayerCC) Marshal() ([]byte, error) {\n\theader, err := t.Header.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpayload := make([]byte, t.MarshalSize()-headerLength)\n\tbinary.BigEndian.PutUint32(payload, t.SenderSSRC)\n\tbinary.BigEndian.PutUint32(payload[4:], t.MediaSSRC)\n\tbinary.BigEndian.PutUint16(payload[baseSequenceNumberOffset:], t.BaseSequenceNumber)\n\tbinary.BigEndian.PutUint16(payload[packetStatusCountOffset:], t.PacketStatusCount)\n\tReferenceTimeAndFbPktCount := appendNBitsToUint32(0, 24, t.ReferenceTime)\n\tReferenceTimeAndFbPktCount = appendNBitsToUint32(ReferenceTimeAndFbPktCount, 8, uint32(t.FbPktCount))\n\tbinary.BigEndian.PutUint32(payload[referenceTimeOffset:], ReferenceTimeAndFbPktCount)\n\n\tfor i, chunk := range t.PacketChunks {\n\t\tb, err := chunk.Marshal()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcopy(payload[packetChunkOffset+i*2:], b)\n\t}\n\n\trecvDeltaOffset := packetChunkOffset + len(t.PacketChunks)*2\n\tvar i int\n\tfor _, delta := range t.RecvDeltas {\n\t\tb, err := delta.Marshal()\n\t\tif err == nil {\n\t\t\tcopy(payload[recvDeltaOffset+i:], b)\n\t\t\ti++\n\t\t\tif delta.Type == TypeTCCPacketReceivedLargeDelta {\n\t\t\t\ti++\n\t\t\t}\n\t\t}\n\t}\n\n\tif t.Header.Padding {\n\t\tpayload[len(payload)-1] = uint8(t.MarshalSize() - int(t.packetLen())) //nolint:gosec // G115\n\t}\n\n\treturn append(header, payload...), nil\n}\n\n// Unmarshal ..\n//\n//nolint:gocognit,cyclop\nfunc (t *TransportLayerCC) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) < (headerLength + ssrcLength) {\n\t\treturn errPacketTooShort\n\t}\n\n\tif err := t.Header.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\n\t// https://tools.ietf.org/html/rfc4585#page-33\n\t// header's length + payload's length\n\ttotalLength := 4 * (t.Header.Length + 1)\n\n\tif totalLength < headerLength+packetChunkOffset {\n\t\treturn errPacketTooShort\n\t}\n\n\tif len(rawPacket) < int(totalLength) {\n\t\treturn errPacketTooShort\n\t}\n\n\tif t.Header.Type != TypeTransportSpecificFeedback || t.Header.Count != FormatTCC {\n\t\treturn errWrongType\n\t}\n\n\tt.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])\n\tt.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])\n\tt.BaseSequenceNumber = binary.BigEndian.Uint16(rawPacket[headerLength+baseSequenceNumberOffset:])\n\tt.PacketStatusCount = binary.BigEndian.Uint16(rawPacket[headerLength+packetStatusCountOffset:])\n\tt.ReferenceTime = get24BitsFromBytes(rawPacket[headerLength+referenceTimeOffset : headerLength+referenceTimeOffset+3])\n\tt.FbPktCount = rawPacket[headerLength+fbPktCountOffset]\n\n\tpacketStatusPos := uint16(headerLength + packetChunkOffset)\n\tvar processedPacketNum uint16\n\tfor processedPacketNum < t.PacketStatusCount {\n\t\tif packetStatusPos+packetStatusChunkLength >= totalLength {\n\t\t\treturn errPacketTooShort\n\t\t}\n\t\ttyp := getNBitsFromByte(rawPacket[packetStatusPos : packetStatusPos+1][0], 0, 1)\n\t\tvar iPacketStatus PacketStatusChunk\n\t\tswitch typ {\n\t\tcase TypeTCCRunLengthChunk:\n\t\t\tpacketStatus := &RunLengthChunk{Type: typ}\n\t\t\tiPacketStatus = packetStatus\n\t\t\terr := packetStatus.Unmarshal(rawPacket[packetStatusPos : packetStatusPos+2])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tpacketNumberToProcess := localMin(t.PacketStatusCount-processedPacketNum, packetStatus.RunLength)\n\t\t\tif packetStatus.PacketStatusSymbol == TypeTCCPacketReceivedSmallDelta ||\n\t\t\t\tpacketStatus.PacketStatusSymbol == TypeTCCPacketReceivedLargeDelta {\n\t\t\t\tfor range packetNumberToProcess {\n\t\t\t\t\tt.RecvDeltas = append(t.RecvDeltas, &RecvDelta{Type: packetStatus.PacketStatusSymbol})\n\t\t\t\t}\n\t\t\t}\n\t\t\tprocessedPacketNum += packetNumberToProcess\n\t\tcase TypeTCCStatusVectorChunk:\n\t\t\tpacketStatus := &StatusVectorChunk{Type: typ}\n\t\t\tiPacketStatus = packetStatus\n\t\t\terr := packetStatus.Unmarshal(rawPacket[packetStatusPos : packetStatusPos+2])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif packetStatus.SymbolSize == TypeTCCSymbolSizeOneBit {\n\t\t\t\tfor j := 0; j < len(packetStatus.SymbolList); j++ {\n\t\t\t\t\tif packetStatus.SymbolList[j] == TypeTCCPacketReceivedSmallDelta {\n\t\t\t\t\t\tt.RecvDeltas = append(t.RecvDeltas, &RecvDelta{Type: TypeTCCPacketReceivedSmallDelta})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif packetStatus.SymbolSize == TypeTCCSymbolSizeTwoBit {\n\t\t\t\tfor j := 0; j < len(packetStatus.SymbolList); j++ {\n\t\t\t\t\tif packetStatus.SymbolList[j] == TypeTCCPacketReceivedSmallDelta ||\n\t\t\t\t\t\tpacketStatus.SymbolList[j] == TypeTCCPacketReceivedLargeDelta {\n\t\t\t\t\t\tt.RecvDeltas = append(t.RecvDeltas, &RecvDelta{Type: packetStatus.SymbolList[j]})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tprocessedPacketNum += uint16(len(packetStatus.SymbolList)) //nolint:gosec // G115\n\t\t}\n\t\tpacketStatusPos += packetStatusChunkLength\n\t\tt.PacketChunks = append(t.PacketChunks, iPacketStatus)\n\t}\n\n\trecvDeltasPos := packetStatusPos\n\tfor _, delta := range t.RecvDeltas {\n\t\tif delta.Type == TypeTCCPacketReceivedSmallDelta {\n\t\t\tif recvDeltasPos+1 > totalLength {\n\t\t\t\treturn errPacketTooShort\n\t\t\t}\n\t\t\terr := delta.Unmarshal(rawPacket[recvDeltasPos : recvDeltasPos+1])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\trecvDeltasPos++\n\t\t}\n\t\tif delta.Type == TypeTCCPacketReceivedLargeDelta {\n\t\t\tif recvDeltasPos+2 > totalLength {\n\t\t\t\treturn errPacketTooShort\n\t\t\t}\n\t\t\terr := delta.Unmarshal(rawPacket[recvDeltasPos : recvDeltasPos+2])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\trecvDeltasPos += 2\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (t TransportLayerCC) DestinationSSRC() []uint32 {\n\treturn []uint32{t.MediaSSRC}\n}\n\nfunc localMin(x, y uint16) uint16 {\n\tif x < y {\n\t\treturn x\n\t}\n\n\treturn y\n}\n"
  },
  {
    "path": "transport_layer_cc_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*TransportLayerCC)(nil) // assert is a Packet\n\nfunc TestTransportLayerCC_RunLengthChunkUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      RunLengthChunk\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\t// 3.1.3 example1: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7\n\t\t\tName: \"example1\",\n\t\t\tData: []byte{0, 0xDD},\n\t\t\tWant: RunLengthChunk{\n\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\tPacketStatusSymbol: TypeTCCPacketNotReceived,\n\t\t\t\tRunLength:          221,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\t// 3.1.3 example2: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7\n\t\t\tName: \"example2\",\n\t\t\tData: []byte{0x60, 0x18},\n\t\t\tWant: RunLengthChunk{\n\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedWithoutDelta,\n\t\t\t\tRunLength:          24,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t} {\n\t\tvar chunk RunLengthChunk\n\t\tassert.NoError(t, chunk.Unmarshal(test.Data))\n\t\tassert.Equalf(t, test.Want, chunk, \"Unmarshal %q\", test.Name)\n\t}\n}\n\nfunc TestTransportLayerCC_RunLengthChunkMarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      RunLengthChunk\n\t\tWant      []byte\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\t// 3.1.3 example1: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7\n\t\t\tName: \"example1\",\n\t\t\tData: RunLengthChunk{\n\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\tPacketStatusSymbol: TypeTCCPacketNotReceived,\n\t\t\t\tRunLength:          221,\n\t\t\t},\n\t\t\tWant:      []byte{0, 0xDD},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\t// 3.1.3 example2: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7\n\t\t\tName: \"example2\",\n\t\t\tData: RunLengthChunk{\n\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedWithoutDelta,\n\t\t\t\tRunLength:          24,\n\t\t\t},\n\t\t\tWant:      []byte{0x60, 0x18},\n\t\t\tWantError: nil,\n\t\t},\n\t} {\n\t\tchunk := test.Data\n\t\tdata, _ := chunk.Marshal()\n\t\tassert.Equalf(t, test.Want, data, \"Marshal %q\", test.Name)\n\t}\n}\n\nfunc TestTransportLayerCC_StatusVectorChunkUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      StatusVectorChunk\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\t// 3.1.4 example1: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7\n\t\t\tName: \"example1\",\n\t\t\tData: []byte{0x9F, 0x1C},\n\t\t\tWant: StatusVectorChunk{\n\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\tSymbolSize: TypeTCCSymbolSizeOneBit,\n\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\t// 3.1.4 example2: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7\n\t\t\tName: \"example2\",\n\t\t\tData: []byte{0xCD, 0x50},\n\t\t\tWant: StatusVectorChunk{\n\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\tSymbolSize: TypeTCCSymbolSizeTwoBit,\n\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t} {\n\t\tvar chunk StatusVectorChunk\n\t\tassert.NoErrorf(t, chunk.Unmarshal(test.Data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Want.Type, chunk.Type, \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Want.SymbolSize, chunk.SymbolSize, \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Want.SymbolList, chunk.SymbolList, \"Unmarshal %q\", test.Name)\n\t}\n}\n\nfunc TestTransportLayerCC_StatusVectorChunkMarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      StatusVectorChunk\n\t\tWant      []byte\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\t// 3.1.4 example1: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7\n\t\t\tName: \"example1\",\n\t\t\tData: StatusVectorChunk{\n\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\tSymbolSize: TypeTCCSymbolSizeOneBit,\n\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t},\n\t\t\t},\n\t\t\tWant:      []byte{0x9F, 0x1C},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\t// 3.1.4 example2: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7\n\t\t\tName: \"example2\",\n\t\t\tData: StatusVectorChunk{\n\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\tSymbolSize: TypeTCCSymbolSizeTwoBit,\n\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t},\n\t\t\t},\n\t\t\tWant:      []byte{0xCD, 0x50},\n\t\t\tWantError: nil,\n\t\t},\n\t} {\n\t\tchunk := test.Data\n\t\tdata, _ := chunk.Marshal()\n\t\tassert.Equal(t, test.Want, data, \"Unmarshal %q\", test.Name)\n\t}\n}\n\nfunc TestTransportLayerCC_RecvDeltaUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      RecvDelta\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"small delta 63.75ms\",\n\t\t\tData: []byte{0xFF},\n\t\t\tWant: RecvDelta{\n\t\t\t\tType: TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t// 255 * 250\n\t\t\t\tDelta: 63750,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"big delta 8191.75ms\",\n\t\t\tData: []byte{0x7F, 0xFF},\n\t\t\tWant: RecvDelta{\n\t\t\t\tType: TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t// 32767 * 250\n\t\t\t\tDelta: 8191750,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"big delta -8192ms\",\n\t\t\tData: []byte{0x80, 0x00},\n\t\t\tWant: RecvDelta{\n\t\t\t\tType: TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t// -32768 * 250\n\t\t\t\tDelta: -8192000,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t} {\n\t\tvar chunk RecvDelta\n\t\tassert.NoErrorf(t, chunk.Unmarshal(test.Data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Want, chunk, \"Unmarshal %q\", test.Name)\n\t}\n}\n\nfunc TestTransportLayerCC_RecvDeltaMarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      RecvDelta\n\t\tWant      []byte\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"small delta 63.75ms\",\n\t\t\tData: RecvDelta{\n\t\t\t\tType: TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t// 255 * 250\n\t\t\t\tDelta: 63750,\n\t\t\t},\n\t\t\tWant:      []byte{0xFF},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"big delta 8191.75ms\",\n\t\t\tData: RecvDelta{\n\t\t\t\tType: TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t// 32767 * 250\n\t\t\t\tDelta: 8191750,\n\t\t\t},\n\t\t\tWant:      []byte{0x7F, 0xFF},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"big delta -8192ms\",\n\t\t\tData: RecvDelta{\n\t\t\t\tType: TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t// -32768 * 250\n\t\t\t\tDelta: -8192000,\n\t\t\t},\n\t\t\tWant:      []byte{0x80, 0x00},\n\t\t\tWantError: nil,\n\t\t},\n\t} {\n\t\tchunk := test.Data\n\t\tdata, _ := chunk.Marshal()\n\t\tassert.Equalf(t, test.Want, data, \"Unmarshal %q\", test.Name)\n\t}\n}\n\n// 0                   1                   2                   3\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=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          |  recv delta   |  recv delta   |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// 0b10101111,0b11001101,0b00000000,0b00000101,\n// 0b11111010,0b00010111,0b11111010,0b00010111,\n// 0b01000011,0b00000011,0b00101111,0b10100000,\n// 0b00000000,0b10011001,0b00000000,0b00000001,\n// 0b00111101,0b11101000,0b00000010,0b00010111,\n// 0b00100000,0b00000001,0b10010100,0b00000001,\n// .\n//\n//nolint:maintidx\nfunc TestTransportLayerCC_Unmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      TransportLayerCC\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"example1\",\n\t\t\tData: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x5,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x43, 0x3, 0x2f, 0xa0,\n\t\t\t\t0x0, 0x99, 0x0, 0x1,\n\t\t\t\t0x3d, 0xe8, 0x2, 0x17,\n\t\t\t\t0x20, 0x1, 0x94, 0x1,\n\t\t\t},\n\t\t\tWant: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  5,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          1124282272,\n\t\t\t\tBaseSequenceNumber: 153,\n\t\t\t\tPacketStatusCount:  1,\n\t\t\t\tReferenceTime:      4057090,\n\t\t\t\tFbPktCount:         23,\n\t\t\t\t// 0b00100000, 0b00000001\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&RunLengthChunk{\n\t\t\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tRunLength:          1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t// 0b10010100\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 37000,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example2\",\n\t\t\tData: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x6,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x19, 0x3d, 0xd8, 0xbb,\n\t\t\t\t0x1, 0x74, 0x0, 0xe,\n\t\t\t\t0x45, 0xb1, 0x5a, 0x40,\n\t\t\t\t0xd8, 0x0, 0xf0, 0xff,\n\t\t\t\t0xd0, 0x0, 0x0, 0x3,\n\t\t\t},\n\t\t\tWant: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  6,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          423483579,\n\t\t\t\tBaseSequenceNumber: 372,\n\t\t\t\tPacketStatusCount:  14,\n\t\t\t\tReferenceTime:      4567386,\n\t\t\t\tFbPktCount:         64,\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&StatusVectorChunk{\n\t\t\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\t\t\tSymbolSize: TypeTCCSymbolSizeTwoBit,\n\t\t\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t&StatusVectorChunk{\n\t\t\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\t\t\tSymbolSize: TypeTCCSymbolSizeTwoBit,\n\t\t\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t// 0b10010100\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 52000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\tDelta: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example3\",\n\t\t\tData: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x7,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x19, 0x3d, 0xd8, 0xbb,\n\t\t\t\t0x1, 0x74, 0x0, 0x6,\n\t\t\t\t0x45, 0xb1, 0x5a, 0x40,\n\t\t\t\t0x40, 0x2, 0x20, 0x04,\n\t\t\t\t0x1f, 0xfe, 0x1f, 0x9a,\n\t\t\t\t0xd0, 0x0, 0xd0, 0x0,\n\t\t\t},\n\t\t\tWant: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  7,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          423483579,\n\t\t\t\tBaseSequenceNumber: 372,\n\t\t\t\tPacketStatusCount:  6,\n\t\t\t\tReferenceTime:      4567386,\n\t\t\t\tFbPktCount:         64,\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&RunLengthChunk{\n\t\t\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\tRunLength:          2,\n\t\t\t\t\t},\n\t\t\t\t\t&RunLengthChunk{\n\t\t\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tRunLength:          4,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\tDelta: 2047500,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\tDelta: 2022500,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 52000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 52000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example4\",\n\t\t\tData: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x7,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x19, 0x3d, 0xd8, 0xbb,\n\t\t\t\t0x0, 0x4, 0x0, 0x7,\n\t\t\t\t0x10, 0x63, 0x6e, 0x1,\n\t\t\t\t0x20, 0x7, 0x4c, 0x24,\n\t\t\t\t0x24, 0x10, 0xc, 0xc,\n\t\t\t\t0x10, 0x0, 0x0, 0x3,\n\t\t\t},\n\t\t\tWant: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  7,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          423483579,\n\t\t\t\tBaseSequenceNumber: 4,\n\t\t\t\tPacketStatusCount:  7,\n\t\t\t\tReferenceTime:      1074030,\n\t\t\t\tFbPktCount:         1,\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&RunLengthChunk{\n\t\t\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tRunLength:          7,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 19000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 9000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 9000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 4000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 3000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 3000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 4000,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example5\",\n\t\t\tData: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x6,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x19, 0x3d, 0xd8, 0xbb,\n\t\t\t\t0x0, 0x1, 0x0, 0xe,\n\t\t\t\t0x10, 0x63, 0x6d, 0x0,\n\t\t\t\t0xba, 0x0, 0x10, 0xc,\n\t\t\t\t0xc, 0x10, 0x0, 0x3,\n\t\t\t},\n\t\t\tWant: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  6,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          423483579,\n\t\t\t\tBaseSequenceNumber: 1,\n\t\t\t\tPacketStatusCount:  14,\n\t\t\t\tReferenceTime:      1074029,\n\t\t\t\tFbPktCount:         0,\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&StatusVectorChunk{\n\t\t\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\t\t\tSymbolSize: 0,\n\t\t\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 4000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 3000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 3000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 4000,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example6\",\n\t\t\tData: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x7,\n\t\t\t\t0x9b, 0x74, 0xf6, 0x1f,\n\t\t\t\t0x93, 0x71, 0xdc, 0xbc,\n\t\t\t\t0x85, 0x3c, 0x0, 0x9,\n\t\t\t\t0x63, 0xf9, 0x16, 0xb3,\n\t\t\t\t0xd5, 0x52, 0x0, 0x30,\n\t\t\t\t0x9b, 0xaa, 0x6a, 0xaa,\n\t\t\t\t0x7b, 0x1, 0x9, 0x1,\n\t\t\t},\n\t\t\tWant: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  7,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         2608133663,\n\t\t\t\tMediaSSRC:          2473712828,\n\t\t\t\tBaseSequenceNumber: 34108,\n\t\t\t\tPacketStatusCount:  9,\n\t\t\t\tReferenceTime:      6551830,\n\t\t\t\tFbPktCount:         179,\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&StatusVectorChunk{\n\t\t\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\t\t\tSymbolSize: TypeTCCSymbolSizeTwoBit,\n\t\t\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t&RunLengthChunk{\n\t\t\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\t\t\tPacketStatusSymbol: TypeTCCPacketNotReceived,\n\t\t\t\t\t\tRunLength:          48,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 38750,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 42500,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 26500,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 42500,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 30750,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\tDelta: 66250,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example7\",\n\t\t\tData: []byte{\n\t\t\t\t0x8f, 0xcd, 0x0, 0x4,\n\t\t\t\t0x9a, 0xcb, 0x4, 0x42,\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t},\n\t\t\tWant: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: false,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  4,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         2596996162,\n\t\t\t\tMediaSSRC:          0,\n\t\t\t\tBaseSequenceNumber: 0,\n\t\t\t\tPacketStatusCount:  0,\n\t\t\t\tReferenceTime:      0,\n\t\t\t\tFbPktCount:         0,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example8\",\n\t\t\tData: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x5,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x43, 0x3, 0x2f, 0xa0,\n\t\t\t\t0x0, 0x99, 0x0, 0x3,\n\t\t\t\t0x3d, 0xe8, 0x2, 0x17,\n\t\t\t\t0x20, 0x3, 0x94, 0x1,\n\t\t\t},\n\t\t\tWant:      TransportLayerCC{},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"example9\",\n\t\t\tData: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x5,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x43, 0x3, 0x2f, 0xa0,\n\t\t\t\t0x0, 0x99, 0x0, 0x2,\n\t\t\t\t0x3d, 0xe8, 0x2, 0x17,\n\t\t\t\t0x40, 0x2, 0x94, 0x1,\n\t\t\t},\n\t\t\tWant:      TransportLayerCC{},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t} {\n\t\tt.Run(test.Name, func(t *testing.T) {\n\t\t\tvar chunk TransportLayerCC\n\t\t\terr := chunk.Unmarshal(test.Data)\n\t\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q\", test.Name)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.Equalf(t, test.Want, chunk, \"Unmarshal %q\", test.Name)\n\t\t})\n\t}\n}\n\n//nolint:maintidx\nfunc TestTransportLayerCC_Marshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      TransportLayerCC\n\t\tWant      []byte\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"example1\",\n\t\t\tData: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  5,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          1124282272,\n\t\t\t\tBaseSequenceNumber: 153,\n\t\t\t\tPacketStatusCount:  1,\n\t\t\t\tReferenceTime:      4057090,\n\t\t\t\tFbPktCount:         23,\n\t\t\t\t// 0b00100000, 0b00000001\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&RunLengthChunk{\n\t\t\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tRunLength:          1,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t// 0b10010100\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 37000,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWant: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x5,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x43, 0x3, 0x2f, 0xa0,\n\t\t\t\t0x0, 0x99, 0x0, 0x1,\n\t\t\t\t0x3d, 0xe8, 0x2, 0x17,\n\t\t\t\t0x20, 0x1, 0x94, 0x1,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example2\",\n\t\t\tData: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  6,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          423483579,\n\t\t\t\tBaseSequenceNumber: 372,\n\t\t\t\tPacketStatusCount:  2,\n\t\t\t\tReferenceTime:      4567386,\n\t\t\t\tFbPktCount:         64,\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&StatusVectorChunk{\n\t\t\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\t\t\tSymbolSize: TypeTCCSymbolSizeTwoBit,\n\t\t\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t&StatusVectorChunk{\n\t\t\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\t\t\tSymbolSize: TypeTCCSymbolSizeTwoBit,\n\t\t\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedWithoutDelta,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t// 0b10010100\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 52000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\tDelta: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWant: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x6,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x19, 0x3d, 0xd8, 0xbb,\n\t\t\t\t0x1, 0x74, 0x0, 0x2,\n\t\t\t\t0x45, 0xb1, 0x5a, 0x40,\n\t\t\t\t0xd8, 0x0, 0xf0, 0xff,\n\t\t\t\t0xd0, 0x0, 0x0, 0x1,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example3\",\n\t\t\tData: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  7,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          423483579,\n\t\t\t\tBaseSequenceNumber: 372,\n\t\t\t\tPacketStatusCount:  6,\n\t\t\t\tReferenceTime:      4567386,\n\t\t\t\tFbPktCount:         64,\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&RunLengthChunk{\n\t\t\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\tRunLength:          2,\n\t\t\t\t\t},\n\t\t\t\t\t&RunLengthChunk{\n\t\t\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tRunLength:          4,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\tDelta: 2047500,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedLargeDelta,\n\t\t\t\t\t\tDelta: 2022500,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 52000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 52000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 0,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWant: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x7,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x19, 0x3d, 0xd8, 0xbb,\n\t\t\t\t0x1, 0x74, 0x0, 0x6,\n\t\t\t\t0x45, 0xb1, 0x5a, 0x40,\n\t\t\t\t0x40, 0x2, 0x20, 0x04,\n\t\t\t\t0x1f, 0xfe, 0x1f, 0x9a,\n\t\t\t\t0xd0, 0x0, 0xd0, 0x0,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example4\",\n\t\t\tData: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  7,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          423483579,\n\t\t\t\tBaseSequenceNumber: 4,\n\t\t\t\tPacketStatusCount:  7,\n\t\t\t\tReferenceTime:      1074030,\n\t\t\t\tFbPktCount:         1,\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&RunLengthChunk{\n\t\t\t\t\t\tType:               TypeTCCRunLengthChunk,\n\t\t\t\t\t\tPacketStatusSymbol: TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tRunLength:          7,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 19000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 9000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 9000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 4000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 3000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 3000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 4000,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWant: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x7,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x19, 0x3d, 0xd8, 0xbb,\n\t\t\t\t0x0, 0x4, 0x0, 0x7,\n\t\t\t\t0x10, 0x63, 0x6e, 0x1,\n\t\t\t\t0x20, 0x7, 0x4c, 0x24,\n\t\t\t\t0x24, 0x10, 0xc, 0xc,\n\t\t\t\t0x10, 0x0, 0x0, 0x3,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example5\",\n\t\t\tData: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  6,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          423483579,\n\t\t\t\tBaseSequenceNumber: 1,\n\t\t\t\tPacketStatusCount:  14,\n\t\t\t\tReferenceTime:      1074029,\n\t\t\t\tFbPktCount:         0,\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&StatusVectorChunk{\n\t\t\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\t\t\tSymbolSize: TypeTCCSymbolSizeOneBit,\n\t\t\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 4000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 3000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 3000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 4000,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWant: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x6,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x19, 0x3d, 0xd8, 0xbb,\n\t\t\t\t0x0, 0x1, 0x0, 0xe,\n\t\t\t\t0x10, 0x63, 0x6d, 0x0,\n\t\t\t\t0xba, 0x0, 0x10, 0xc,\n\t\t\t\t0xc, 0x10, 0x0, 0x2,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t\t{\n\t\t\tName: \"example6\",\n\t\t\tData: TransportLayerCC{\n\t\t\t\tHeader: Header{\n\t\t\t\t\tPadding: true,\n\t\t\t\t\tCount:   FormatTCC,\n\t\t\t\t\tType:    TypeTransportSpecificFeedback,\n\t\t\t\t\tLength:  7,\n\t\t\t\t},\n\t\t\t\tSenderSSRC:         4195875351,\n\t\t\t\tMediaSSRC:          1124282272,\n\t\t\t\tBaseSequenceNumber: 39956,\n\t\t\t\tPacketStatusCount:  12,\n\t\t\t\tReferenceTime:      7701536,\n\t\t\t\tFbPktCount:         0,\n\t\t\t\tPacketChunks: []PacketStatusChunk{\n\t\t\t\t\t&StatusVectorChunk{\n\t\t\t\t\t\tType:       TypeTCCStatusVectorChunk,\n\t\t\t\t\t\tSymbolSize: TypeTCCSymbolSizeOneBit,\n\t\t\t\t\t\tSymbolList: []uint16{\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t\tTypeTCCPacketNotReceived,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tRecvDeltas: []*RecvDelta{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 48250,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 15750,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 14750,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 15750,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 20750,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 36000,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:  TypeTCCPacketReceivedSmallDelta,\n\t\t\t\t\t\tDelta: 14750,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWant: []byte{\n\t\t\t\t0xaf, 0xcd, 0x0, 0x7,\n\t\t\t\t0xfa, 0x17, 0xfa, 0x17,\n\t\t\t\t0x43, 0x3, 0x2f, 0xa0,\n\t\t\t\t0x9c, 0x14, 0x0, 0xc,\n\t\t\t\t0x75, 0x84, 0x20, 0x0,\n\n\t\t\t\t0xbe, 0xc0, 0xc1, 0x3f,\n\t\t\t\t0x3b, 0x3f, 0x53, 0x90,\n\t\t\t\t0x3b, 0x0, 0x0, 0x3,\n\t\t\t},\n\t\t\tWantError: nil,\n\t\t},\n\t} {\n\t\tt.Run(test.Name, func(t *testing.T) {\n\t\t\tbin, err := test.Data.Marshal()\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equalf(t, test.Want, bin, \"Marshal %q\", test.Name)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "transport_layer_nack.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"math\"\n\t\"strings\"\n)\n\n// PacketBitmap shouldn't be used like a normal integral,\n// so it's type is masked here. Access it with PacketList().\ntype PacketBitmap uint16\n\n// NackPair is a wire-representation of a collection of\n// Lost RTP packets.\ntype NackPair struct {\n\t// ID of lost packets\n\tPacketID uint16\n\n\t// Bitmask of following lost packets\n\tLostPackets PacketBitmap\n}\n\n// The TransportLayerNack packet informs the encoder about the loss of a transport packet\n// IETF RFC 4585, Section 6.2.1\n// https://tools.ietf.org/html/rfc4585#section-6.2.1\ntype TransportLayerNack struct {\n\t// SSRC of sender\n\tSenderSSRC uint32\n\n\t// SSRC of the media source\n\tMediaSSRC uint32\n\n\tNacks []NackPair\n}\n\n// NackPairsFromSequenceNumbers generates a slice of NackPair from a list of SequenceNumbers\n// This handles generating the proper values for PacketID/LostPackets.\nfunc NackPairsFromSequenceNumbers(sequenceNumbers []uint16) (pairs []NackPair) {\n\tif len(sequenceNumbers) == 0 {\n\t\treturn []NackPair{}\n\t}\n\n\tnackPair := &NackPair{PacketID: sequenceNumbers[0]}\n\tfor i := 1; i < len(sequenceNumbers); i++ {\n\t\tm := sequenceNumbers[i]\n\n\t\tif m-nackPair.PacketID > 16 {\n\t\t\tpairs = append(pairs, *nackPair)\n\t\t\tnackPair = &NackPair{PacketID: m}\n\n\t\t\tcontinue\n\t\t}\n\n\t\tnackPair.LostPackets |= 1 << (m - nackPair.PacketID - 1)\n\t}\n\tpairs = append(pairs, *nackPair)\n\n\treturn\n}\n\n// Range calls f sequentially for each sequence number covered by n.\n// If f returns false, Range stops the iteration.\nfunc (n *NackPair) Range(f func(seqno uint16) bool) {\n\tmore := f(n.PacketID)\n\tif !more {\n\t\treturn\n\t}\n\n\tb := n.LostPackets\n\tfor i := uint16(0); b != 0; i++ {\n\t\tif (b & (1 << i)) != 0 {\n\t\t\tb &^= (1 << i)\n\t\t\tmore = f(n.PacketID + i + 1)\n\t\t\tif !more {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\n// PacketList returns a list of Nack'd packets that's referenced by a NackPair.\nfunc (n *NackPair) PacketList() []uint16 {\n\tout := make([]uint16, 0, 17)\n\tn.Range(func(seqno uint16) bool {\n\t\tout = append(out, seqno)\n\n\t\treturn true\n\t})\n\n\treturn out\n}\n\nconst (\n\ttlnLength  = 2\n\tnackOffset = 8\n)\n\n// Marshal encodes the TransportLayerNack in binary.\nfunc (p TransportLayerNack) Marshal() ([]byte, error) {\n\tif len(p.Nacks)+tlnLength > math.MaxUint8 {\n\t\treturn nil, errTooManyReports\n\t}\n\n\trawPacket := make([]byte, nackOffset+(len(p.Nacks)*4))\n\tbinary.BigEndian.PutUint32(rawPacket, p.SenderSSRC)\n\tbinary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC)\n\tfor i := 0; i < len(p.Nacks); i++ {\n\t\tbinary.BigEndian.PutUint16(rawPacket[nackOffset+(4*i):], p.Nacks[i].PacketID)\n\t\tbinary.BigEndian.PutUint16(rawPacket[nackOffset+(4*i)+2:], uint16(p.Nacks[i].LostPackets))\n\t}\n\th := p.Header()\n\thData, err := h.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn append(hData, rawPacket...), nil\n}\n\n// Unmarshal decodes the TransportLayerNack from binary.\nfunc (p *TransportLayerNack) Unmarshal(rawPacket []byte) error {\n\tif len(rawPacket) < (headerLength + ssrcLength) {\n\t\treturn errPacketTooShort\n\t}\n\n\tvar header Header\n\tif err := header.Unmarshal(rawPacket); err != nil {\n\t\treturn err\n\t}\n\n\tif len(rawPacket) < (headerLength + int(4*header.Length)) {\n\t\treturn errPacketTooShort\n\t}\n\n\tif header.Type != TypeTransportSpecificFeedback || header.Count != FormatTLN {\n\t\treturn errWrongType\n\t}\n\n\t// The FCI field MUST contain at least one and MAY contain more than one Generic NACK\n\tif 4*header.Length <= nackOffset {\n\t\treturn errBadLength\n\t}\n\n\tp.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])\n\tp.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])\n\tfor i := headerLength + nackOffset; i < (headerLength + int(header.Length*4)); i += 4 {\n\t\tp.Nacks = append(p.Nacks, NackPair{\n\t\t\tbinary.BigEndian.Uint16(rawPacket[i:]),\n\t\t\tPacketBitmap(binary.BigEndian.Uint16(rawPacket[i+2:])),\n\t\t})\n\t}\n\n\treturn nil\n}\n\n// MarshalSize returns the size of the packet once marshaled.\nfunc (p *TransportLayerNack) MarshalSize() int {\n\treturn headerLength + nackOffset + (len(p.Nacks) * 4)\n}\n\n// Header returns the Header associated with this packet.\nfunc (p *TransportLayerNack) Header() Header {\n\treturn Header{\n\t\tCount:  FormatTLN,\n\t\tType:   TypeTransportSpecificFeedback,\n\t\tLength: uint16((p.MarshalSize() / 4) - 1), //nolint:gosec // G115\n\t}\n}\n\nfunc (p TransportLayerNack) String() string {\n\tvar out strings.Builder\n\tfmt.Fprintf(&out, \"TransportLayerNack from %x\\n\", p.SenderSSRC)\n\tfmt.Fprintf(&out, \"\\tMedia Ssrc %x\\n\", p.MediaSSRC)\n\tout.WriteString(\"\\tID\\tLostPackets\\n\")\n\tfor _, i := range p.Nacks {\n\t\tfmt.Fprintf(&out, \"\\t%d\\t%b\\n\", i.PacketID, i.LostPackets)\n\t}\n\n\treturn out.String()\n}\n\n// DestinationSSRC returns an array of SSRC values that this packet refers to.\nfunc (p *TransportLayerNack) DestinationSSRC() []uint32 {\n\treturn []uint32{p.MediaSSRC}\n}\n"
  },
  {
    "path": "transport_layer_nack_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar _ Packet = (*TransportLayerNack)(nil) // assert is a Packet\n\nfunc TestTransportLayerNackUnmarshal(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tData      []byte\n\t\tWant      TransportLayerNack\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tData: []byte{\n\t\t\t\t// TransportLayerNack\n\t\t\t\t0x81, 0xcd, 0x0, 0x3,\n\t\t\t\t// sender=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// media=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// nack 0xAAAA, 0x5555\n\t\t\t\t0xaa, 0xaa, 0x55, 0x55,\n\t\t\t},\n\t\t\tWant: TransportLayerNack{\n\t\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t\t\tNacks:      []NackPair{{0xaaaa, 0x5555}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"short report\",\n\t\t\tData: []byte{\n\t\t\t\t0x81, 0xcd, 0x0, 0x2,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// report ends early\n\t\t\t},\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t\t{\n\t\t\tName: \"bad length\",\n\t\t\tData: []byte{\n\t\t\t\t// TransportLayerNack\n\t\t\t\t0x81, 0xcd, 0x0, 0x2,\n\t\t\t\t// sender=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// media=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t},\n\t\t\tWantError: errBadLength,\n\t\t},\n\t\t{\n\t\t\tName: \"wrong type\",\n\t\t\tData: []byte{\n\t\t\t\t// v=2, p=0, count=1, SR, len=7\n\t\t\t\t0x81, 0xc8, 0x0, 0x7,\n\t\t\t\t// ssrc=0x902f9e2e\n\t\t\t\t0x90, 0x2f, 0x9e, 0x2e,\n\t\t\t\t// ssrc=0xbc5e9a40\n\t\t\t\t0xbc, 0x5e, 0x9a, 0x40,\n\t\t\t\t// fracLost=0, totalLost=0\n\t\t\t\t0x0, 0x0, 0x0, 0x0,\n\t\t\t\t// lastSeq=0x46e1\n\t\t\t\t0x0, 0x0, 0x46, 0xe1,\n\t\t\t\t// jitter=273\n\t\t\t\t0x0, 0x0, 0x1, 0x11,\n\t\t\t\t// lsr=0x9f36432\n\t\t\t\t0x9, 0xf3, 0x64, 0x32,\n\t\t\t\t// delay=150137\n\t\t\t\t0x0, 0x2, 0x4a, 0x79,\n\t\t\t},\n\t\t\tWantError: errWrongType,\n\t\t},\n\t\t{\n\t\t\tName:      \"nil\",\n\t\t\tData:      nil,\n\t\t\tWantError: errPacketTooShort,\n\t\t},\n\t} {\n\t\tvar tln TransportLayerNack\n\t\terr := tln.Unmarshal(test.Data)\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Want, tln, \"Unmarshal %q\", test.Name)\n\t}\n}\n\nfunc TestTransportLayerNackRoundTrip(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName      string\n\t\tReport    TransportLayerNack\n\t\tWantError error\n\t}{\n\t\t{\n\t\t\tName: \"valid\",\n\t\t\tReport: TransportLayerNack{\n\t\t\t\tSenderSSRC: 0x902f9e2e,\n\t\t\t\tMediaSSRC:  0x902f9e2e,\n\t\t\t\tNacks:      []NackPair{{1, 0xAA}, {1034, 0x05}},\n\t\t\t},\n\t\t},\n\t} {\n\t\tdata, err := test.Report.Marshal()\n\t\tassert.ErrorIsf(t, err, test.WantError, \"Marshal err on %q\", test.Name)\n\n\t\tvar decoded TransportLayerNack\n\t\tassert.NoErrorf(t, decoded.Unmarshal(data), \"Unmarshal %q\", test.Name)\n\t\tassert.Equalf(t, test.Report, decoded, \"Unmarshal %q: decoded mismatch\", test.Name)\n\t}\n}\n\nfunc testNackPair(t *testing.T, s []uint16, n NackPair) {\n\tt.Helper()\n\n\tl := n.PacketList()\n\tassert.Equalf(t, s, l, \"NackPair %v mismatch\", n)\n}\n\nfunc TestNackPair(t *testing.T) {\n\ttestNackPair(t, []uint16{42}, NackPair{42, 0})\n\ttestNackPair(t, []uint16{42, 43}, NackPair{42, 1})\n\ttestNackPair(t, []uint16{42, 44}, NackPair{42, 2})\n\ttestNackPair(t, []uint16{42, 43, 44}, NackPair{42, 3})\n\ttestNackPair(t, []uint16{42, 42 + 16}, NackPair{42, 0x8000})\n}\n\nfunc TestNackPairRange(t *testing.T) {\n\tpair := NackPair{42, 2}\n\n\tout := make([]uint16, 0)\n\tpair.Range(func(s uint16) bool {\n\t\tout = append(out, s)\n\n\t\treturn true\n\t})\n\tassert.Equal(t, []uint16{42, 44}, out)\n\n\tout = make([]uint16, 0)\n\tpair.Range(func(s uint16) bool {\n\t\tout = append(out, s)\n\n\t\treturn false\n\t})\n\tassert.Equal(t, []uint16{42}, out)\n}\n\nfunc TestTransportLayerNackPairGeneration(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tName            string\n\t\tSequenceNumbers []uint16\n\t\tExpected        []NackPair\n\t}{\n\t\t{\n\t\t\t\"No Sequence Numbers\",\n\t\t\t[]uint16{},\n\t\t\t[]NackPair{},\n\t\t},\n\t\t{\n\t\t\t\"Single Sequence Number\",\n\t\t\t[]uint16{100},\n\t\t\t[]NackPair{\n\t\t\t\t{PacketID: 100, LostPackets: 0x0},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Multiple in range, Single NACKPair\",\n\t\t\t[]uint16{100, 101, 105, 115},\n\t\t\t[]NackPair{\n\t\t\t\t{PacketID: 100, LostPackets: 0x4011},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Multiple Ranges, Multiple NACKPair\",\n\t\t\t[]uint16{100, 117, 500, 501, 502},\n\t\t\t[]NackPair{\n\t\t\t\t{PacketID: 100, LostPackets: 0},\n\t\t\t\t{PacketID: 117, LostPackets: 0},\n\t\t\t\t{PacketID: 500, LostPackets: 0x3},\n\t\t\t},\n\t\t},\n\t} {\n\t\tactual := NackPairsFromSequenceNumbers(test.SequenceNumbers)\n\t\tassert.Equalf(t, test.Expected, actual, \"%q NackPair generation mismatch\", test.Name)\n\t}\n}\n"
  },
  {
    "path": "util.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\n// getPadding Returns the padding required to make the length a multiple of 4.\nfunc getPadding(packetLen int) int {\n\tif packetLen%4 == 0 {\n\t\treturn 0\n\t}\n\n\treturn 4 - (packetLen % 4)\n}\n\n// setNBitsOfUint16 will truncate the value to size, left-shift to startIndex position and set.\nfunc setNBitsOfUint16(src, size, startIndex, val uint16) (uint16, error) {\n\tif startIndex+size > 16 {\n\t\treturn 0, errInvalidSizeOrStartIndex\n\t}\n\n\t// truncate val to size bits\n\tval &= (1 << size) - 1\n\n\treturn src | (val << (16 - size - startIndex)), nil\n}\n\n// appendBit32 will left-shift and append n bits of val.\nfunc appendNBitsToUint32(src, n, val uint32) uint32 {\n\treturn (src << n) | (val & (0xFFFFFFFF >> (32 - n)))\n}\n\n// getNBit get n bits from 1 byte, begin with a position.\nfunc getNBitsFromByte(b byte, begin, n uint16) uint16 {\n\tendShift := 8 - (begin + n)\n\tmask := (0xFF >> begin) & uint8(0xFF<<endShift)\n\n\treturn uint16(b&mask) >> endShift\n}\n\n// get24BitFromBytes get 24bits from `[3]byte` slice.\nfunc get24BitsFromBytes(b []byte) uint32 {\n\treturn uint32(b[0])<<16 + uint32(b[1])<<8 + uint32(b[2])\n}\n"
  },
  {
    "path": "util_test.go",
    "content": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetPadding(t *testing.T) {\n\tassert := assert.New(t)\n\ttype testCase struct {\n\t\tinput  int\n\t\tresult int\n\t}\n\n\tcases := []testCase{\n\t\t{input: 0, result: 0},\n\t\t{input: 1, result: 3},\n\t\t{input: 2, result: 2},\n\t\t{input: 3, result: 1},\n\t\t{input: 4, result: 0},\n\t\t{input: 100, result: 0},\n\t\t{input: 500, result: 0},\n\t}\n\tfor _, testCase := range cases {\n\t\tassert.Equalf(\n\t\t\tgetPadding(testCase.input), testCase.result, \"Test case returned wrong value for input %d\", testCase.input,\n\t\t)\n\t}\n}\n\nfunc TestSetNBitsOfUint16(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tname        string\n\t\tsource      uint16\n\t\tsize        uint16\n\t\tindex       uint16\n\t\tvalue       uint16\n\t\tresult      uint16\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\t\"setOneBit\", 0, 1, 8, 1, 128, nil,\n\t\t},\n\t\t{\n\t\t\t\"setStatusVectorBit\", 0, 1, 0, 1, 32768, nil,\n\t\t},\n\t\t{\n\t\t\t\"setStatusVectorSecondBit\", 32768, 1, 1, 1, 49152, nil,\n\t\t},\n\t\t{\n\t\t\t\"setStatusVectorInnerBitsAndCutValue\", 49152, 2, 6, 11111, 49920, nil,\n\t\t},\n\t\t{\n\t\t\t\"setRunLengthSecondTwoBit\", 32768, 2, 1, 1, 40960, nil,\n\t\t},\n\t\t{\n\t\t\t\"setOneBitOutOfBounds\", 32768, 2, 15, 1, 0, errInvalidSizeOrStartIndex,\n\t\t},\n\t} {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tgot, err := setNBitsOfUint16(test.source, test.size, test.index, test.value)\n\t\t\tif test.expectedErr != nil {\n\t\t\t\tassert.ErrorIs(t, err, test.expectedErr)\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t}\n\n\t\t\tassert.Equalf(t, test.result, got, \"setNBitsOfUint16 %q\", test.name)\n\t\t})\n\t}\n}\n"
  }
]