Full Code of pion/rtcp for AI

main d61b52c3dcee cached
76 files
301.9 KB
101.3k tokens
408 symbols
1 requests
Download .txt
Showing preview only (322K chars total). Download the full file or copy to clipboard to get everything.
Repository: pion/rtcp
Branch: main
Commit: d61b52c3dcee
Files: 76
Total size: 301.9 KB

Directory structure:
gitextract_env3fvgh/

├── .github/
│   ├── .gitignore
│   ├── fetch-scripts.sh
│   ├── install-hooks.sh
│   └── workflows/
│       ├── api.yaml
│       ├── codeql-analysis.yml
│       ├── fuzz.yaml
│       ├── lint.yaml
│       ├── release.yml
│       ├── renovate-go-sum-fix.yaml
│       ├── reuse.yml
│       ├── test.yaml
│       └── tidy-check.yaml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .reuse/
│   └── dep5
├── LICENSE
├── LICENSES/
│   ├── CC0-1.0.txt
│   └── MIT.txt
├── README.md
├── application_defined.go
├── application_defined_test.go
├── codecov.yml
├── compound_packet.go
├── compound_packet_test.go
├── doc.go
├── errors.go
├── extended_report.go
├── extended_report_test.go
├── full_intra_request.go
├── full_intra_request_test.go
├── fuzz_test.go
├── go.mod
├── go.sum
├── goodbye.go
├── goodbye_test.go
├── header.go
├── header_test.go
├── packet.go
├── packet_buffer.go
├── packet_buffer_test.go
├── packet_stringifier.go
├── packet_stringifier_test.go
├── packet_test.go
├── picture_loss_indication.go
├── picture_loss_indication_test.go
├── rapid_resynchronization_request.go
├── rapid_resynchronization_request_test.go
├── raw_packet.go
├── raw_packet_test.go
├── receiver_estimated_maximum_bitrate.go
├── receiver_estimated_maximum_bitrate_test.go
├── receiver_report.go
├── receiver_report_test.go
├── reception_report.go
├── renovate.json
├── rfc8888.go
├── rfc8888_test.go
├── sender_report.go
├── sender_report_test.go
├── slice_loss_indication.go
├── slice_loss_indication_test.go
├── source_description.go
├── source_description_test.go
├── testdata/
│   └── fuzz/
│       └── FuzzUnmarshal/
│           ├── 0b954a73147600a3
│           ├── 16c369bd58290097
│           ├── 5eaf215c68e1ddb3
│           ├── 60753346a105d3c3
│           ├── 6366fbb9980fa33a
│           └── e1a48af9f8e7db71
├── transport_layer_cc.go
├── transport_layer_cc_test.go
├── transport_layer_nack.go
├── transport_layer_nack_test.go
├── util.go
└── util_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/.gitignore
================================================
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

.goassets


================================================
FILE: .github/fetch-scripts.sh
================================================
#!/bin/sh

#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

set -eu

SCRIPT_PATH="$(realpath "$(dirname "$0")")"
GOASSETS_PATH="${SCRIPT_PATH}/.goassets"

GOASSETS_REF=${GOASSETS_REF:-main}

if [ -d "${GOASSETS_PATH}" ]; then
  if ! git -C "${GOASSETS_PATH}" diff --exit-code; then
    echo "${GOASSETS_PATH} has uncommitted changes" >&2
    exit 1
  fi
  git -C "${GOASSETS_PATH}" fetch origin
  git -C "${GOASSETS_PATH}" checkout ${GOASSETS_REF}
  git -C "${GOASSETS_PATH}" reset --hard origin/${GOASSETS_REF}
else
  git clone -b ${GOASSETS_REF} https://github.com/pion/.goassets.git "${GOASSETS_PATH}"
fi


================================================
FILE: .github/install-hooks.sh
================================================
#!/bin/sh

#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

SCRIPT_PATH="$(realpath "$(dirname "$0")")"

. ${SCRIPT_PATH}/fetch-scripts.sh

cp "${GOASSETS_PATH}/hooks/commit-msg.sh" "${SCRIPT_PATH}/../.git/hooks/commit-msg"
cp "${GOASSETS_PATH}/hooks/pre-commit.sh" "${SCRIPT_PATH}/../.git/hooks/pre-commit"


================================================
FILE: .github/workflows/api.yaml
================================================
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
# If this repository should have package specific CI config,
# remove the repository name from .goassets/.github/workflows/assets-sync.yml.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

name: API
on:
  pull_request:

jobs:
  check:
    uses: pion/.goassets/.github/workflows/api.reusable.yml@main


================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
# If this repository should have package specific CI config,
# remove the repository name from .goassets/.github/workflows/assets-sync.yml.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

name: CodeQL

on:
  workflow_dispatch:
  schedule:
    - cron: '23 5 * * 0'
  pull_request:
    branches:
      - main
    paths:
      - '**.go'

jobs:
  analyze:
    uses: pion/.goassets/.github/workflows/codeql-analysis.reusable.yml@main


================================================
FILE: .github/workflows/fuzz.yaml
================================================
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
# If this repository should have package specific CI config,
# remove the repository name from .goassets/.github/workflows/assets-sync.yml.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

name: Fuzz
on:
  push:
    branches:
      - main
  schedule:
    - cron: "0 */8 * * *"

jobs:
  fuzz:
    uses: pion/.goassets/.github/workflows/fuzz.reusable.yml@main
    with:
      go-version: "1.25" # auto-update/latest-go-version
      fuzz-time: "60s"


================================================
FILE: .github/workflows/lint.yaml
================================================
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
# If this repository should have package specific CI config,
# remove the repository name from .goassets/.github/workflows/assets-sync.yml.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

name: Lint
on:
  pull_request:

jobs:
  lint:
    uses: pion/.goassets/.github/workflows/lint.reusable.yml@main
    with:
      golangci-lint-version: v2.10.1


================================================
FILE: .github/workflows/release.yml
================================================
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
# If this repository should have package specific CI config,
# remove the repository name from .goassets/.github/workflows/assets-sync.yml.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

name: Release
on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    uses: pion/.goassets/.github/workflows/release.reusable.yml@main
    with:
      go-version: "1.25" # auto-update/latest-go-version


================================================
FILE: .github/workflows/renovate-go-sum-fix.yaml
================================================
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
# If this repository should have package specific CI config,
# remove the repository name from .goassets/.github/workflows/assets-sync.yml.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

name: Fix go.sum
on:
  push:
    branches:
      - renovate/*

jobs:
  fix:
    uses: pion/.goassets/.github/workflows/renovate-go-sum-fix.reusable.yml@main
    secrets:
      token: ${{ secrets.PIONBOT_PRIVATE_KEY }}


================================================
FILE: .github/workflows/reuse.yml
================================================
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
# If this repository should have package specific CI config,
# remove the repository name from .goassets/.github/workflows/assets-sync.yml.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

name: REUSE Compliance Check

on:
  push:
  pull_request:

jobs:
  lint:
    uses: pion/.goassets/.github/workflows/reuse.reusable.yml@main


================================================
FILE: .github/workflows/test.yaml
================================================
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
# If this repository should have package specific CI config,
# remove the repository name from .goassets/.github/workflows/assets-sync.yml.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

name: Test
on:
  push:
    branches:
      - main
  pull_request:

jobs:
  test:
    uses: pion/.goassets/.github/workflows/test.reusable.yml@main
    strategy:
      matrix:
        go: ["1.25", "1.24"] # auto-update/supported-go-version-list
      fail-fast: false
    with:
      go-version: ${{ matrix.go }}
    secrets: inherit

  test-i386:
    uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@main
    strategy:
      matrix:
        go: ["1.25", "1.24"] # auto-update/supported-go-version-list
      fail-fast: false
    with:
      go-version: ${{ matrix.go }}

  test-windows:
    uses: pion/.goassets/.github/workflows/test-windows.reusable.yml@main
    strategy:
      matrix:
        go: ["1.25", "1.24"] # auto-update/supported-go-version-list
      fail-fast: false
    with:
      go-version: ${{ matrix.go }}

  test-macos:
    uses: pion/.goassets/.github/workflows/test-macos.reusable.yml@main
    strategy:
      matrix:
        go: ["1.25", "1.24"] # auto-update/supported-go-version-list
      fail-fast: false
    with:
      go-version: ${{ matrix.go }}

  test-wasm:
    uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@main
    with:
      go-version: "1.25" # auto-update/latest-go-version
    secrets: inherit


================================================
FILE: .github/workflows/tidy-check.yaml
================================================
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
# If this repository should have package specific CI config,
# remove the repository name from .goassets/.github/workflows/assets-sync.yml.
#
# If you want to update the shared CI config, send a PR to
# https://github.com/pion/.goassets instead of this repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

name: Go mod tidy
on:
  pull_request:
  push:
    branches:
      - main

jobs:
  tidy:
    uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@main
    with:
      go-version: "1.25" # auto-update/latest-go-version


================================================
FILE: .gitignore
================================================
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

### JetBrains IDE ###
#####################
.idea/

### Emacs Temporary Files ###
#############################
*~

### Folders ###
###############
bin/
vendor/
node_modules/

### Files ###
#############
*.ivf
*.ogg
tags
cover.out
*.sw[poe]
*.wasm
examples/sfu-ws/cert.pem
examples/sfu-ws/key.pem
wasm_exec.js


================================================
FILE: .golangci.yml
================================================
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

version: "2"
linters:
  enable:
    - asciicheck       # Simple linter to check that your code does not contain non-ASCII identifiers
    - bidichk          # Checks for dangerous unicode character sequences
    - bodyclose        # checks whether HTTP response body is closed successfully
    - containedctx     # containedctx is a linter that detects struct contained context.Context field
    - contextcheck     # check the function whether use a non-inherited context
    - cyclop           # checks function and package cyclomatic complexity
    - decorder         # check declaration order and count of types, constants, variables and functions
    - dogsled          # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
    - dupl             # Tool for code clone detection
    - durationcheck    # check for two durations multiplied together
    - err113           # Golang linter to check the errors handling expressions
    - errcheck         # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
    - 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.
    - errname          # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.
    - 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.
    - exhaustive       # check exhaustiveness of enum switch statements
    - forbidigo        # Forbids identifiers
    - forcetypeassert  # finds forced type assertions
    - gochecknoglobals # Checks that no globals are present in Go code
    - gocognit         # Computes and checks the cognitive complexity of functions
    - goconst          # Finds repeated strings that could be replaced by a constant
    - gocritic         # The most opinionated Go source code linter
    - gocyclo          # Computes and checks the cyclomatic complexity of functions
    - godot            # Check if comments end in a period
    - godox            # Tool for detection of FIXME, TODO and other comment keywords
    - goheader         # Checks is file header matches to pattern
    - gomoddirectives  # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.
    - goprintffuncname # Checks that printf-like functions are named with `f` at the end
    - gosec            # Inspects source code for security problems
    - govet            # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
    - grouper          # An analyzer to analyze expression groups.
    - importas         # Enforces consistent import aliases
    - ineffassign      # Detects when assignments to existing variables are not used
    - lll              # Reports long lines
    - maintidx         # maintidx measures the maintainability index of each function.
    - makezero         # Finds slice declarations with non-zero initial length
    - misspell         # Finds commonly misspelled English words in comments
    - modernize        # Replace and suggests simplifications to code
    - nakedret         # Finds naked returns in functions greater than a specified function length
    - nestif           # Reports deeply nested if statements
    - nilerr           # Finds the code that returns nil even if it checks that the error is not nil.
    - nilnil           # Checks that there is no simultaneous return of `nil` error and an invalid value.
    - nlreturn         # nlreturn checks for a new line before return and branch statements to increase code clarity
    - noctx            # noctx finds sending http request without context.Context
    - predeclared      # find code that shadows one of Go's predeclared identifiers
    - revive           # golint replacement, finds style mistakes
    - staticcheck      # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
    - tagliatelle      # Checks the struct tags.
    - thelper          # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
    - unconvert        # Remove unnecessary type conversions
    - unparam          # Reports unused function parameters
    - unused           # Checks Go code for unused constants, variables, functions and types
    - varnamelen       # checks that the length of a variable's name matches its scope
    - wastedassign     # wastedassign finds wasted assignment statements
    - whitespace       # Tool for detection of leading and trailing whitespace
  disable:
    - depguard         # Go linter that checks if package imports are in a list of acceptable packages
    - funlen           # Tool for detection of long functions
    - gochecknoinits   # Checks that no init functions are present in Go code
    - 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.
    - interfacebloat   # A linter that checks length of interface.
    - ireturn          # Accept Interfaces, Return Concrete Types
    - mnd              # An analyzer to detect magic numbers
    - nolintlint       # Reports ill-formed or insufficient nolint directives
    - paralleltest     # paralleltest detects missing usage of t.Parallel() method in your Go test
    - prealloc         # Finds slice declarations that could potentially be preallocated
    - promlinter       # Check Prometheus metrics naming via promlint
    - rowserrcheck     # checks whether Err of rows is checked successfully
    - sqlclosecheck    # Checks that sql.Rows and sql.Stmt are closed.
    - testpackage      # linter that makes you use a separate _test package
    - tparallel        # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
    - wrapcheck        # Checks that errors returned from external packages are wrapped
    - wsl              # Whitespace Linter - Forces you to use empty lines!
  settings:
    staticcheck:
      checks:
        - all
        - -QF1008 # "could remove embedded field", to keep it explicit!
        - -QF1003 # "could use tagged switch on enum", Cases conflicts with exhaustive!
    exhaustive:
      default-signifies-exhaustive: true
    forbidigo:
      forbid:
        - pattern: ^fmt.Print(f|ln)?$
        - pattern: ^log.(Panic|Fatal|Print)(f|ln)?$
        - pattern: ^os.Exit$
        - pattern: ^panic$
        - pattern: ^print(ln)?$
        - pattern: ^testing.T.(Error|Errorf|Fatal|Fatalf|Fail|FailNow)$
          pkg: ^testing$
          msg: use testify/assert instead
      analyze-types: true
    gomodguard:
      blocked:
        modules:
          - github.com/pkg/errors:
              recommendations:
                - errors
    govet:
      enable:
        - shadow
    revive:
      rules:
        # Prefer 'any' type alias over 'interface{}' for Go 1.18+ compatibility
        - name: use-any
          severity: warning
          disabled: false
    misspell:
      locale: US
    varnamelen:
      max-distance: 12
      min-name-length: 2
      ignore-type-assert-ok: true
      ignore-map-index-ok: true
      ignore-chan-recv-ok: true
      ignore-decls:
        - i int
        - n int
        - w io.Writer
        - r io.Reader
        - b []byte
  exclusions:
    generated: lax
    rules:
      - linters:
          - forbidigo
          - gocognit
        path: (examples|main\.go)
      - linters:
          - gocognit
        path: _test\.go
      - linters:
          - forbidigo
        path: cmd
formatters:
  enable:
    - gci              # Gci control golang package import order and make it always deterministic.
    - gofmt            # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
    - gofumpt          # Gofumpt checks whether code was gofumpt-ed.
    - goimports        # Goimports does everything that gofmt does. Additionally it checks unused imports
  exclusions:
    generated: lax
issues:
  max-issues-per-linter: 0
  max-same-issues: 0

================================================
FILE: .goreleaser.yml
================================================
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

builds:
- skip: true


================================================
FILE: .reuse/dep5
================================================
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: Pion
Source: https://github.com/pion/

Files: 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
Copyright: 2026 The Pion community <https://pion.ly>
License: MIT

Files: testdata/seed/* testdata/fuzz/* **/testdata/fuzz/* api/*.txt
Copyright: 2026 The Pion community <https://pion.ly>
License: CC0-1.0


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2026 The Pion community <https://pion.ly>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: LICENSES/CC0-1.0.txt
================================================
Creative Commons Legal Code

CC0 1.0 Universal

    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
    HEREUNDER.

Statement of Purpose

The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").

Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.

For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.

1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:

  i. the right to reproduce, adapt, distribute, perform, display,
     communicate, and translate a Work;
 ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
     likeness depicted in a Work;
 iv. rights protecting against unfair competition in regards to a Work,
     subject to the limitations in paragraph 4(a), below;
  v. rights protecting the extraction, dissemination, use and reuse of data
     in a Work;
 vi. database rights (such as those arising under Directive 96/9/EC of the
     European Parliament and of the Council of 11 March 1996 on the legal
     protection of databases, and under any national implementation
     thereof, including any amended or successor version of such
     directive); and
vii. other similar, equivalent or corresponding rights throughout the
     world based on applicable law or treaty, and any national
     implementations thereof.

2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.

3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.

4. Limitations and Disclaimers.

 a. No trademark or patent rights held by Affirmer are waived, abandoned,
    surrendered, licensed or otherwise affected by this document.
 b. Affirmer offers the Work as-is and makes no representations or
    warranties of any kind concerning the Work, express, implied,
    statutory or otherwise, including without limitation warranties of
    title, merchantability, fitness for a particular purpose, non
    infringement, or the absence of latent or other defects, accuracy, or
    the present or absence of errors, whether or not discoverable, all to
    the greatest extent permissible under applicable law.
 c. Affirmer disclaims responsibility for clearing rights of other persons
    that may apply to the Work or any use thereof, including without
    limitation any person's Copyright and Related Rights in the Work.
    Further, Affirmer disclaims responsibility for obtaining any necessary
    consents, permissions or other rights required for any use of the
    Work.
 d. Affirmer understands and acknowledges that Creative Commons is not a
    party to this document and has no duty or obligation with respect to
    this CC0 or use of the Work.


================================================
FILE: LICENSES/MIT.txt
================================================
MIT License

Copyright (c) <year> <copyright holders>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
<h1 align="center">
  <br>
  Pion RTCP
  <br>
</h1>
<h4 align="center">A Go implementation of RTCP</h4>
<p align="center">
  <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-rtcp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion RTCP"></a>
  <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>
  <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>
  <br>
  <img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/rtcp/test.yaml">
  <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>
  <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>
  <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>
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
</p>
<br>

See [DESIGN.md](DESIGN.md) for an overview of features and future goals.

### Roadmap
The 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.

### Community
Pion has an active community on the [Discord](https://discord.gg/PngbdqpFbt).

Follow the [Pion Bluesky](https://bsky.app/profile/pion.ly) or [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.

We are always looking to support **your projects**. Please reach out if you have something to build!
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)

### Contributing
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible

### License
MIT License - see [LICENSE](LICENSE) for full text


================================================
FILE: application_defined.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"encoding/binary"
)

// ApplicationDefined represents an RTCP application-defined packet.
type ApplicationDefined struct {
	SubType uint8
	SSRC    uint32
	Name    string
	Data    []byte
}

// DestinationSSRC returns the SSRC value for this packet.
func (a ApplicationDefined) DestinationSSRC() []uint32 {
	return []uint32{a.SSRC}
}

// Marshal serializes the application-defined struct into a byte slice with padding.
func (a ApplicationDefined) Marshal() ([]byte, error) {
	dataLength := len(a.Data)
	if dataLength > 0xFFFF-12 {
		return nil, errAppDefinedDataTooLarge
	}
	if len(a.Name) != 4 {
		return nil, errAppDefinedInvalidName
	}
	// Calculate the padding size to be added to make the packet length a multiple of 4 bytes.
	paddingSize := 4 - (dataLength % 4)
	if paddingSize == 4 {
		paddingSize = 0
	}

	packetSize := a.MarshalSize()
	header := Header{
		Type:    TypeApplicationDefined,
		Length:  uint16((packetSize / 4) - 1), //nolint:gosec // G115
		Padding: paddingSize != 0,
		Count:   a.SubType,
	}

	headerBytes, err := header.Marshal()
	if err != nil {
		return nil, err
	}

	rawPacket := make([]byte, packetSize)
	copy(rawPacket, headerBytes)
	binary.BigEndian.PutUint32(rawPacket[4:8], a.SSRC)
	copy(rawPacket[8:12], a.Name)
	copy(rawPacket[12:], a.Data)

	// Add padding if necessary.
	if paddingSize > 0 {
		for i := 0; i < paddingSize; i++ {
			rawPacket[12+dataLength+i] = byte(paddingSize)
		}
	}

	return rawPacket, nil
}

// Unmarshal parses the given raw packet into an application-defined struct, handling padding.
func (a *ApplicationDefined) Unmarshal(rawPacket []byte) error {
	/*
	    0                   1                   2                   3
	    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
	  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |V=2|P| subtype |   PT=APP=204  |             length            |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |                           SSRC/CSRC                           |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |                          name (ASCII)                         |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |                   application-dependent data                ...
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	*/
	header := Header{}
	err := header.Unmarshal(rawPacket)
	if err != nil {
		return err
	}
	if len(rawPacket) < 12 {
		return errPacketTooShort
	}

	if int(header.Length+1)*4 != len(rawPacket) {
		return errAppDefinedInvalidLength
	}

	a.SubType = header.Count
	a.SSRC = binary.BigEndian.Uint32(rawPacket[4:8])
	a.Name = string(rawPacket[8:12])

	// Check for padding.
	paddingSize := 0
	if header.Padding {
		paddingSize = int(rawPacket[len(rawPacket)-1])
		if paddingSize > len(rawPacket)-12 {
			return errWrongPadding
		}
	}

	a.Data = rawPacket[12 : len(rawPacket)-paddingSize]

	return nil
}

// MarshalSize returns the size of the packet once marshaled.
func (a *ApplicationDefined) MarshalSize() int {
	dataLength := len(a.Data)
	// Calculate the padding size to be added to make the packet length a multiple of 4 bytes.
	paddingSize := 4 - (dataLength % 4)
	if paddingSize == 4 {
		paddingSize = 0
	}

	return 12 + dataLength + paddingSize
}


================================================
FILE: application_defined_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestTApplicationPacketUnmarshal(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Data      []byte
		Want      ApplicationDefined
		WantError error
	}{
		{
			Name: "valid",
			Data: []byte{
				// Application Packet Type + Length(0x0003)
				0x80, 0xcc, 0x00, 0x03,
				// sender=0x4baae1ab
				0x4b, 0xaa, 0xe1, 0xab,
				// name='NAME'
				0x4E, 0x41, 0x4D, 0x45,
				// data='ABCD'
				0x41, 0x42, 0x43, 0x44,
			},
			Want: ApplicationDefined{
				SubType: 0,
				SSRC:    0x4baae1ab,
				Name:    "NAME",
				Data:    []byte{0x41, 0x42, 0x43, 0x44},
			},
		},
		{
			Name: "validCustomSsubType",
			Data: []byte{
				// Application Packet Type (SubType 31) + Length(0x0003)
				0x9f, 0xcc, 0x00, 0x03,
				// sender=0x4baae1ab
				0x4b, 0xaa, 0xe1, 0xab,
				// name='NAME'
				0x4E, 0x41, 0x4D, 0x45,
				// data='ABCD'
				0x41, 0x42, 0x43, 0x44,
			},
			Want: ApplicationDefined{
				SubType: 31,
				SSRC:    0x4baae1ab,
				Name:    "NAME",
				Data:    []byte{0x41, 0x42, 0x43, 0x44},
			},
		},
		{
			Name: "validWithPadding",
			Data: []byte{
				// Application Packet Type + Length(0x0002)  (0xA0 has padding bit set)
				0xA0, 0xcc, 0x00, 0x04,
				// sender=0x4baae1ab
				0x4b, 0xaa, 0xe1, 0xab,
				// name='NAME'
				0x4E, 0x41, 0x4D, 0x45,
				// data='ABCDE'
				0x41, 0x42, 0x43, 0x44, 0x45,
				// 3 bytes padding as packet length must be a division of 4
				0x03, 0x03, 0x03,
			},
			Want: ApplicationDefined{
				SubType: 0,
				SSRC:    0x4baae1ab,
				Name:    "NAME",
				Data:    []byte{0x41, 0x42, 0x43, 0x44, 0x45},
			},
		},
		{
			Name: "invalidAppPacketLengthField",
			Data: []byte{
				// Application Packet Type + invalid Length(0x00FF)
				0x80, 0xcc, 0x00, 0xFF,
				// sender=0x4baae1ab
				0x4b, 0xaa, 0xe1, 0xab,
				// name='NAME'
				0x4E, 0x41, 0x4D, 0x45,
				// data='ABCD'
				0x41, 0x42, 0x43, 0x44,
			},
			WantError: errAppDefinedInvalidLength,
		},
		{
			Name: "invalidPacketLengthTooShort",
			Data: []byte{
				// Application Packet Type + Length(0x0002). Total packet length is less than 12 bytes
				0x80, 0xcc, 0x00, 0x2,
				// sender=0x4baae1ab
				0x4b, 0xaa, 0xe1, 0xab,
				// name='SUI'
				0x53, 0x55, 0x49,
			},
			WantError: errPacketTooShort,
		},
		{
			Name: "wrongPaddingSize",
			Data: []byte{
				// Application Packet Type + Length(0x0002)  (0xA0 has padding bit set)
				0xA0, 0xcc, 0x00, 0x04,
				// sender=0x4baae1ab
				0x4b, 0xaa, 0xe1, 0xab,
				// name='NAME'
				0x4E, 0x41, 0x4D, 0x45,
				// data='ABCDE'
				0x41, 0x42, 0x43, 0x44, 0x45,
				// 3 bytes padding as packet length must be a division of 4
				0x03, 0x03, 0x09, // last byte has padding size 0x09 which is more than the data + padding bytes
			},
			WantError: errWrongPadding,
		},
		{
			Name: "invalidHeader",
			Data: []byte{
				// Application Packet Type + invalid Length(0x00FF)
				0xFF,
			},
			WantError: errPacketTooShort,
		},
	} {
		var apk ApplicationDefined
		err := apk.Unmarshal(test.Data)
		assert.ErrorIsf(t, err, test.WantError, "Unmarshal %q", test.Name)
		if err != nil {
			continue
		}

		assert.Equalf(t, test.Want, apk, "Unmarshal %q", test.Name)

		// Check SSRC is matching
		assert.Equalf(t, uint32(0x4baae1ab), apk.SSRC, "%q SSRC mismatch", test.Name)
		assert.Equalf(t, uint32(0x4baae1ab), apk.DestinationSSRC()[0], "%q DestinationSSRC mismatch", test.Name)
	}
}

func TestTApplicationPacketMarshal(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Want      []byte
		Packet    ApplicationDefined
		WantError error
	}{
		{
			Name: "valid",
			Want: []byte{
				// Application Packet Type + Length(0x0003)
				0x80, 0xcc, 0x00, 0x03,
				// sender=0x4baae1ab
				0x4b, 0xaa, 0xe1, 0xab,
				// name='NAME'
				0x4E, 0x41, 0x4D, 0x45,
				// data='ABCD'
				0x41, 0x42, 0x43, 0x44,
			},
			Packet: ApplicationDefined{
				SSRC: 0x4baae1ab,
				Name: "NAME",
				Data: []byte{0x41, 0x42, 0x43, 0x44},
			},
		},
		{
			Name: "validCustomSubType",
			Want: []byte{
				// Application Packet Type (SubType 31) + Length(0x0003)
				0x9f, 0xcc, 0x00, 0x03,
				// sender=0x4baae1ab
				0x4b, 0xaa, 0xe1, 0xab,
				// name='NAME'
				0x4E, 0x41, 0x4D, 0x45,
				// data='ABCD'
				0x41, 0x42, 0x43, 0x44,
			},
			Packet: ApplicationDefined{
				SubType: 31,
				SSRC:    0x4baae1ab,
				Name:    "NAME",
				Data:    []byte{0x41, 0x42, 0x43, 0x44},
			},
		},
		{
			Name: "validWithPadding",
			Want: []byte{
				// Application Packet Type + Length(0x0002)  (0xA0 has padding bit set)
				0xA0, 0xcc, 0x00, 0x04,
				// sender=0x4baae1ab
				0x4b, 0xaa, 0xe1, 0xab,
				// name='NAME'
				0x4E, 0x41, 0x4D, 0x45,
				// data='ABCDE'
				0x41, 0x42, 0x43, 0x44, 0x45,
				// 3 bytes padding as packet length must be a division of 4
				0x03, 0x03, 0x03,
			},
			Packet: ApplicationDefined{
				SSRC: 0x4baae1ab,
				Name: "NAME",
				Data: []byte{0x41, 0x42, 0x43, 0x44, 0x45},
			},
		},
		{
			Name:      "invalidDataTooLarge",
			WantError: errAppDefinedDataTooLarge,
			Packet: ApplicationDefined{
				SSRC: 0x4baae1ab,
				Name: "NAME",
				Data: make([]byte, 0xFFFF-12+1), // total max packet size is 0xFFFF including header and other fields.
			},
		},
		{
			Name:      "invalidName",
			WantError: errAppDefinedInvalidName,
			Packet: ApplicationDefined{
				SSRC: 0x4baae1ab,
				Name: "NOT4CHARS",
				Data: []byte{0x41, 0x42, 0x43, 0x44},
			},
		},
		{
			Name:      "InvalidSubType",
			WantError: errInvalidHeader,
			Packet: ApplicationDefined{
				SubType: 32, // Must be up to 31
				SSRC:    0x4baae1ab,
				Name:    "NAME",
				Data:    []byte{0x41, 0x42, 0x43, 0x44},
			},
		},
	} {
		rawPacket, err := test.Packet.Marshal()
		assert.ErrorIsf(t, err, test.WantError, "Marshal %q", test.Name)
		if err != nil {
			continue
		}

		assert.Equalf(t, test.Want, rawPacket, "Marshal %q", test.Name)

		marshalSize := test.Packet.MarshalSize()
		assert.Equalf(t, marshalSize, len(rawPacket), "MarshalSize %q", test.Name)
	}
}


================================================
FILE: codecov.yml
================================================
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
#
# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

coverage:
  status:
    project:
      default:
        # Allow decreasing 2% of total coverage to avoid noise.
        threshold: 2%
    patch:
      default:
        target: 70%
        only_pulls: true

ignore:
  - "examples/*"
  - "examples/**/*"


================================================
FILE: compound_packet.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"fmt"
	"strings"
)

// A CompoundPacket is a collection of RTCP packets transmitted as a single packet with
// the underlying protocol (for example UDP).
//
// To maximize the resolution of receiption statistics, the first Packet in a CompoundPacket
// must always be either a SenderReport or a ReceiverReport.  This is true even if no data
// has been sent or received, in which case an empty ReceiverReport must be sent, and even
// if the only other RTCP packet in the compound packet is a Goodbye.
//
// Next, a SourceDescription containing a CNAME item must be included in each CompoundPacket
// to identify the source and to begin associating media for purposes such as lip-sync.
//
// Other RTCP packet types may follow in any order. Packet types may appear more than once.
type CompoundPacket []Packet

// Validate returns an error if this is not an RFC-compliant CompoundPacket.
//
//nolint:cyclop
func (c CompoundPacket) Validate() error {
	if len(c) == 0 {
		return errEmptyCompound
	}

	// SenderReport and ReceiverReport are the only types that
	// are allowed to be the first packet in a compound datagram
	switch c[0].(type) {
	case *SenderReport, *ReceiverReport:
		// ok
	default:
		return errBadFirstPacket
	}

	for _, pkt := range c[1:] {
		switch p := pkt.(type) {
		// If the number of RecetpionReports exceeds 31 additional ReceiverReports
		// can be included here.
		case *ReceiverReport:
			continue

		// A SourceDescription containing a CNAME must be included in every
		// CompoundPacket.
		case *SourceDescription:
			var hasCNAME bool
			for _, c := range p.Chunks {
				for _, it := range c.Items {
					if it.Type == SDESCNAME {
						hasCNAME = true
					}
				}
			}

			if !hasCNAME {
				return errMissingCNAME
			}

			return nil

		// Other packets are not permitted before the CNAME
		default:
			return errPacketBeforeCNAME
		}
	}

	// CNAME never reached
	return errMissingCNAME
}

// CNAME returns the CNAME that *must* be present in every CompoundPacket.
func (c CompoundPacket) CNAME() (string, error) {
	var err error

	if len(c) < 1 {
		return "", errEmptyCompound
	}

	for _, pkt := range c[1:] {
		sdes, ok := pkt.(*SourceDescription)
		if ok {
			for _, c := range sdes.Chunks {
				for _, it := range c.Items {
					if it.Type == SDESCNAME {
						return it.Text, err
					}
				}
			}
		} else {
			_, ok := pkt.(*ReceiverReport)
			if !ok {
				err = errPacketBeforeCNAME
			}
		}
	}

	return "", errMissingCNAME
}

// Marshal encodes the CompoundPacket as binary.
func (c CompoundPacket) Marshal() ([]byte, error) {
	if err := c.Validate(); err != nil {
		return nil, err
	}

	p := []Packet(c)

	return Marshal(p)
}

// MarshalSize returns the size of the packet once marshaled.
func (c CompoundPacket) MarshalSize() int {
	l := 0
	for _, p := range c {
		l += p.MarshalSize()
	}

	return l
}

// Unmarshal decodes a CompoundPacket from binary.
func (c *CompoundPacket) Unmarshal(rawData []byte) error {
	out := make(CompoundPacket, 0)
	for len(rawData) != 0 {
		p, processed, err := unmarshal(rawData)
		if err != nil {
			return err
		}

		out = append(out, p)
		rawData = rawData[processed:]
	}
	*c = out

	return c.Validate()
}

// DestinationSSRC returns the synchronization sources associated with this
// CompoundPacket's reception report.
func (c CompoundPacket) DestinationSSRC() []uint32 {
	if len(c) == 0 {
		return nil
	}

	return c[0].DestinationSSRC()
}

func (c CompoundPacket) String() string {
	out := "CompoundPacket\n"
	for _, p := range c {
		stringer, canString := p.(fmt.Stringer)
		if canString {
			out += stringer.String()
		} else {
			out += stringify(p)
		}
	}
	out = strings.TrimSuffix(strings.ReplaceAll(out, "\n", "\n\t"), "\t")

	return out
}


================================================
FILE: compound_packet_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

var _ Packet = (*CompoundPacket)(nil) // assert is a Packet

func TestReadEOF(t *testing.T) {
	shortHeader := []byte{
		0x81, 0xc9, // missing type & len
	}

	_, err := Unmarshal(shortHeader)
	assert.Error(t, err)
}

func TestBadCompound(t *testing.T) {
	// trailing data!
	badcompound := realPacket()[:34]
	packets, err := Unmarshal(badcompound)
	assert.Error(t, err)

	assert.Nil(t, packets)

	badcompound = realPacket()[84:104]

	packets, err = Unmarshal(badcompound)
	assert.NoError(t, err)

	compound := CompoundPacket(packets)

	// this should return an error,
	// it violates the "must start with RR or SR" rule
	assert.ErrorIs(t, compound.Validate(), errBadFirstPacket)
	assert.Equal(t, 2, len(compound))

	_, ok := compound[0].(*Goodbye)
	assert.True(t, ok)

	_, ok = compound[1].(*PictureLossIndication)
	assert.True(t, ok)
}

func TestValidPacket(t *testing.T) {
	cname := NewCNAMESourceDescription(1234, "cname")

	for _, test := range []struct {
		Name   string
		Packet CompoundPacket
		Err    error
	}{
		{
			Name:   "empty",
			Packet: CompoundPacket{},
			Err:    errEmptyCompound,
		},
		{
			Name: "no cname",
			Packet: CompoundPacket{
				&SenderReport{},
			},
			Err: errMissingCNAME,
		},
		{
			Name: "just BYE",
			Packet: CompoundPacket{
				&Goodbye{},
			},
			Err: errBadFirstPacket,
		},
		{
			Name: "SDES / no cname",
			Packet: CompoundPacket{
				&SenderReport{},
				&SourceDescription{},
			},
			Err: errMissingCNAME,
		},
		{
			Name: "just SR",
			Packet: CompoundPacket{
				&SenderReport{},
				cname,
			},
			Err: nil,
		},
		{
			Name: "multiple SRs",
			Packet: CompoundPacket{
				&SenderReport{},
				&SenderReport{},
				cname,
			},
			Err: errPacketBeforeCNAME,
		},
		{
			Name: "just RR",
			Packet: CompoundPacket{
				&ReceiverReport{},
				cname,
			},
			Err: nil,
		},
		{
			Name: "multiple RRs",
			Packet: CompoundPacket{
				&ReceiverReport{},
				&ReceiverReport{},
				cname,
			},
			Err: nil,
		},
		{
			Name: "goodbye",
			Packet: CompoundPacket{
				&ReceiverReport{},
				cname,
				&Goodbye{},
			},
			Err: nil,
		},
	} {
		assert.ErrorIsf(t, test.Packet.Validate(), test.Err, "Validate(%s)", test.Name)
	}
}

func TestCNAME(t *testing.T) {
	cname := NewCNAMESourceDescription(1234, "cname")

	for _, test := range []struct {
		Name   string
		Packet CompoundPacket
		Err    error
		Text   string
	}{
		{
			Name: "no cname",
			Packet: CompoundPacket{
				&SenderReport{},
			},
			Err: errMissingCNAME,
		},
		{
			Name: "SDES / no cname",
			Packet: CompoundPacket{
				&SenderReport{},
				&SourceDescription{},
			},
			Err: errMissingCNAME,
		},
		{
			Name: "just SR",
			Packet: CompoundPacket{
				&SenderReport{},
				cname,
			},
			Err:  nil,
			Text: "cname",
		},
		{
			Name: "multiple SRs",
			Packet: CompoundPacket{
				&SenderReport{},
				&SenderReport{},
				cname,
			},
			Err:  errPacketBeforeCNAME,
			Text: "cname",
		},
		{
			Name: "just RR",
			Packet: CompoundPacket{
				&ReceiverReport{},
				cname,
			},
			Err:  nil,
			Text: "cname",
		},
		{
			Name: "multiple RRs",
			Packet: CompoundPacket{
				&ReceiverReport{},
				&ReceiverReport{},
				cname,
			},
			Err:  nil,
			Text: "cname",
		},
		{
			Name: "goodbye",
			Packet: CompoundPacket{
				&ReceiverReport{},
				cname,
				&Goodbye{},
			},
			Err:  nil,
			Text: "cname",
		},
	} {
		assert.ErrorIsf(t, test.Packet.Validate(), test.Err, "Validate(%s)", test.Name)

		name, err := test.Packet.CNAME()
		assert.ErrorIsf(t, err, test.Err, "CNAME(%s)", test.Name)
		assert.Equalf(t, test.Text, name, "CNAME(%s)", test.Name)
	}
}

func TestCompoundPacketRoundTrip(t *testing.T) {
	cname := NewCNAMESourceDescription(1234, "cname")

	for _, test := range []struct {
		Name   string
		Packet CompoundPacket
		Err    error
	}{
		{
			Name: "bye",
			Packet: CompoundPacket{
				&ReceiverReport{},
				cname,
				&Goodbye{
					Sources: []uint32{1234},
				},
			},
		},
		{
			Name: "no cname",
			Packet: CompoundPacket{
				&ReceiverReport{},
			},
			Err: errMissingCNAME,
		},
	} {
		data, err := test.Packet.Marshal()
		assert.ErrorIsf(t, err, test.Err, "Marshal(%v)", test.Name)
		if err != nil {
			continue
		}

		var c CompoundPacket
		assert.NoErrorf(t, c.Unmarshal(data), "Unmarshal(%v)", test.Name)

		data2, err := c.Marshal()
		assert.NoErrorf(t, err, "Marshal(%v)", test.Name)
		assert.Equalf(t, data, data2, "Marshal(%v) mismatch", test.Name)
	}
}


================================================
FILE: doc.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

/*
Package rtcp implements encoding and decoding of RTCP packets according to RFCs 3550 and 5506.

RTCP is a sister protocol of the Real-time Transport Protocol (RTP). Its basic functionality
and packet structure is defined in RFC 3550. RTCP provides out-of-band statistics and control
information for an RTP session. It partners with RTP in the delivery and packaging of multimedia data,
but does not transport any media data itself.

The primary function of RTCP is to provide feedback on the quality of service (QoS)
in media distribution by periodically sending statistics information such as transmitted octet
and packet counts, packet loss, packet delay variation, and round-trip delay time to participants
in a streaming multimedia session. An application may use this information to control quality of
service parameters, perhaps by limiting flow, or using a different codec.

Decoding RTCP packets:

	pkts, err := rtcp.Unmarshal(rtcpData)
	// ...
	for _, pkt := range pkts {
		switch p := pkt.(type) {
		case *rtcp.CompoundPacket:
			...
		case *rtcp.PictureLossIndication:
			...
		default:
			...
		}
	}

Encoding RTCP packets:

	pkt := &rtcp.PictureLossIndication{
		SenderSSRC: senderSSRC,
		MediaSSRC: mediaSSRC
	}
	pliData, err := pkt.Marshal()
	// ...
*/
package rtcp


================================================
FILE: errors.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import "errors"

var (
	errWrongMarshalSize         = errors.New("rtcp: wrong marshal size")
	errInvalidTotalLost         = errors.New("rtcp: invalid total lost count")
	errInvalidHeader            = errors.New("rtcp: invalid header")
	errEmptyCompound            = errors.New("rtcp: empty compound packet")
	errBadFirstPacket           = errors.New("rtcp: first packet in compound must be SR or RR")
	errMissingCNAME             = errors.New("rtcp: compound missing SourceDescription with CNAME")
	errPacketBeforeCNAME        = errors.New("rtcp: feedback packet seen before CNAME")
	errTooManySSRCs             = errors.New("rtcp: too many SSRCs")
	errTooManyReports           = errors.New("rtcp: too many reports")
	errTooManyChunks            = errors.New("rtcp: too many chunks")
	errTooManySources           = errors.New("rtcp: too many sources")
	errPacketTooShort           = errors.New("rtcp: packet too short")
	errWrongType                = errors.New("rtcp: wrong packet type")
	errSDESTextTooLong          = errors.New("rtcp: sdes must be < 255 octets long")
	errSDESMissingType          = errors.New("rtcp: sdes item missing type")
	errReasonTooLong            = errors.New("rtcp: reason must be < 255 octets long")
	errBadVersion               = errors.New("rtcp: invalid packet version")
	errBadLength                = errors.New("rtcp: invalid packet length")
	errWrongPadding             = errors.New("rtcp: invalid padding value")
	errWrongFeedbackType        = errors.New("rtcp: wrong feedback message type")
	errWrongPayloadType         = errors.New("rtcp: wrong payload type")
	errHeaderTooSmall           = errors.New("rtcp: header length is too small")
	errSSRCMustBeZero           = errors.New("rtcp: media SSRC must be 0")
	errMissingREMBidentifier    = errors.New("missing REMB identifier")
	errSSRCNumAndLengthMismatch = errors.New("SSRC num and length do not match")
	errInvalidSizeOrStartIndex  = errors.New("invalid size or startIndex")
	errInvalidBitrate           = errors.New("invalid bitrate")
	errWrongChunkType           = errors.New("rtcp: wrong chunk type")
	errBadStructMemberType      = errors.New("rtcp: struct contains unexpected member type")
	errBadReadParameter         = errors.New("rtcp: cannot read into non-pointer")
	errAppDefinedInvalidLength  = errors.New("rtcp: application defined type invalid length")
	errAppDefinedDataTooLarge   = errors.New("rtcp: application defined data is too large")
	errAppDefinedInvalidName    = errors.New("rtcp: application defined name must be 4 ASCII chars")
)


================================================
FILE: extended_report.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"fmt"
)

// The ExtendedReport packet is an Implementation of RTCP Extended
// Reports defined in RFC 3611. It is used to convey detailed
// information about an RTP stream. Each packet contains one or
// more report blocks, each of which conveys a different kind of
// information.
//
//	0                   1                   2                   3
//	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
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |V=2|P|reserved |   PT=XR=207   |             length            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                              SSRC                             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// :                         report blocks                         :
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// .
type ExtendedReport struct {
	SenderSSRC uint32 `fmt:"0x%X"`
	Reports    []ReportBlock
}

// ReportBlock represents a single report within an ExtendedReport
// packet.
type ReportBlock interface {
	DestinationSSRC() []uint32
	setupBlockHeader()
	unpackBlockHeader()
}

// TypeSpecificField as described in RFC 3611 section 4.5. In typical
// cases, users of ExtendedReports shouldn't need to access this,
// and should instead use the corresponding fields in the actual
// report blocks themselves.
type TypeSpecificField uint8

// XRHeader defines the common fields that must appear at the start
// of each report block. In typical cases, users of ExtendedReports
// shouldn't need to access this. For locally-constructed report
// blocks, these values will not be accurate until the corresponding
// packet is marshaled.
type XRHeader struct {
	BlockType    BlockTypeType
	TypeSpecific TypeSpecificField `fmt:"0x%X"`
	BlockLength  uint16
}

// BlockTypeType specifies the type of report in a report block.
type BlockTypeType uint8

// Extended Report block types from RFC 3611.
const (
	LossRLEReportBlockType               = 1 // RFC 3611, section 4.1
	DuplicateRLEReportBlockType          = 2 // RFC 3611, section 4.2
	PacketReceiptTimesReportBlockType    = 3 // RFC 3611, section 4.3
	ReceiverReferenceTimeReportBlockType = 4 // RFC 3611, section 4.4
	DLRRReportBlockType                  = 5 // RFC 3611, section 4.5
	StatisticsSummaryReportBlockType     = 6 // RFC 3611, section 4.6
	VoIPMetricsReportBlockType           = 7 // RFC 3611, section 4.7
)

// String converts the Extended report block types into readable strings.
func (t BlockTypeType) String() string {
	switch t {
	case LossRLEReportBlockType:
		return "LossRLEReportBlockType"
	case DuplicateRLEReportBlockType:
		return "DuplicateRLEReportBlockType"
	case PacketReceiptTimesReportBlockType:
		return "PacketReceiptTimesReportBlockType"
	case ReceiverReferenceTimeReportBlockType:
		return "ReceiverReferenceTimeReportBlockType"
	case DLRRReportBlockType:
		return "DLRRReportBlockType"
	case StatisticsSummaryReportBlockType:
		return "StatisticsSummaryReportBlockType"
	case VoIPMetricsReportBlockType:
		return "VoIPMetricsReportBlockType"
	}

	return fmt.Sprintf("invalid value %d", t)
}

// rleReportBlock defines the common structure used by both
// Loss RLE report blocks (RFC 3611 §4.1) and Duplicate RLE
// report blocks (RFC 3611 §4.2).
//
//	0                   1                   2                   3
//	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
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  BT = 1 or 2  | rsvd. |   T   |         block length          |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                        SSRC of source                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          begin_seq            |             end_seq           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          chunk 1              |             chunk 2           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// :                              ...                              :
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          chunk n-1            |             chunk n           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// .
type rleReportBlock struct {
	XRHeader
	T        uint8  `encoding:"omit"`
	SSRC     uint32 `fmt:"0x%X"`
	BeginSeq uint16
	EndSeq   uint16
	Chunks   []Chunk
}

// Chunk as defined in RFC 3611, section 4.1. These represent information
// about packet losses and packet duplication. They have three representations:
//
// Run Length Chunk:
//
//	 0                   1
//	 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
//	+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//	|C|R|        run length         |
//	+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Bit Vector Chunk:
//
//	 0                   1
//	 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
//	+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//	|C|        bit vector           |
//	+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Terminating Null Chunk:
//
//	 0                   1
//	 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
//	+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//	|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|
//	+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type Chunk uint16

// LossRLEReportBlock is used to report information about packet
// losses, as described in RFC 3611, section 4.1.
type LossRLEReportBlock rleReportBlock

// DestinationSSRC returns an array of SSRC values that this report block refers to.
func (b *LossRLEReportBlock) DestinationSSRC() []uint32 {
	return []uint32{b.SSRC}
}

func (b *LossRLEReportBlock) setupBlockHeader() {
	b.XRHeader.BlockType = LossRLEReportBlockType
	b.XRHeader.TypeSpecific = TypeSpecificField(b.T & 0x0F)
	b.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115
}

func (b *LossRLEReportBlock) unpackBlockHeader() {
	b.T = uint8(b.XRHeader.TypeSpecific) & 0x0F
}

// DuplicateRLEReportBlock is used to report information about packet
// duplication, as described in RFC 3611, section 4.1.
type DuplicateRLEReportBlock rleReportBlock

// DestinationSSRC returns an array of SSRC values that this report block refers to.
func (b *DuplicateRLEReportBlock) DestinationSSRC() []uint32 {
	return []uint32{b.SSRC}
}

func (b *DuplicateRLEReportBlock) setupBlockHeader() {
	b.XRHeader.BlockType = DuplicateRLEReportBlockType
	b.XRHeader.TypeSpecific = TypeSpecificField(b.T & 0x0F)
	b.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115
}

func (b *DuplicateRLEReportBlock) unpackBlockHeader() {
	b.T = uint8(b.XRHeader.TypeSpecific) & 0x0F
}

// ChunkType enumerates the three kinds of chunks described in RFC 3611 section 4.1.
type ChunkType uint8

// These are the valid values that ChunkType can assume.
const (
	RunLengthChunkType       = 0
	BitVectorChunkType       = 1
	TerminatingNullChunkType = 2
)

func (c Chunk) String() string {
	switch c.Type() {
	case RunLengthChunkType:
		runType, _ := c.RunType()

		return fmt.Sprintf("[RunLength type=%d, length=%d]", runType, c.Value())
	case BitVectorChunkType:
		return fmt.Sprintf("[BitVector 0b%015b]", c.Value())
	case TerminatingNullChunkType:
		return "[TerminatingNull]"
	}

	return fmt.Sprintf("[0x%X]", uint16(c))
}

// Type returns the ChunkType that this Chunk represents.
func (c Chunk) Type() ChunkType {
	if c == 0 {
		return TerminatingNullChunkType
	}

	return ChunkType(c >> 15) //nolint:gosec // G115
}

// RunType returns the RunType that this Chunk represents. It is
// only valid if ChunkType is RunLengthChunkType.
func (c Chunk) RunType() (uint, error) {
	if c.Type() != RunLengthChunkType {
		return 0, errWrongChunkType
	}

	return uint((c >> 14) & 0x01), nil
}

// Value returns the value represented in this Chunk.
func (c Chunk) Value() uint {
	switch c.Type() {
	case RunLengthChunkType:
		return uint(c & 0x3FFF)
	case BitVectorChunkType:
		return uint(c & 0x7FFF)
	case TerminatingNullChunkType:
		return 0
	}

	return uint(c)
}

// PacketReceiptTimesReportBlock represents a Packet Receipt Times
// report block, as described in RFC 3611 section 4.3.
//
//	0                   1                   2                   3
//	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
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     BT=3      | rsvd. |   T   |         block length          |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                        SSRC of source                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          begin_seq            |             end_seq           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |       Receipt time of packet begin_seq                        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |       Receipt time of packet (begin_seq + 1) mod 65536        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// :                              ...                              :
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |       Receipt time of packet (end_seq - 1) mod 65536          |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// .
type PacketReceiptTimesReportBlock struct {
	XRHeader
	T           uint8  `encoding:"omit"`
	SSRC        uint32 `fmt:"0x%X"`
	BeginSeq    uint16
	EndSeq      uint16
	ReceiptTime []uint32
}

// DestinationSSRC returns an array of SSRC values that this report block refers to.
func (b *PacketReceiptTimesReportBlock) DestinationSSRC() []uint32 {
	return []uint32{b.SSRC}
}

func (b *PacketReceiptTimesReportBlock) setupBlockHeader() {
	b.XRHeader.BlockType = PacketReceiptTimesReportBlockType
	b.XRHeader.TypeSpecific = TypeSpecificField(b.T & 0x0F)
	b.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115
}

func (b *PacketReceiptTimesReportBlock) unpackBlockHeader() {
	b.T = uint8(b.XRHeader.TypeSpecific) & 0x0F
}

// ReceiverReferenceTimeReportBlock encodes a Receiver Reference Time
// report block as described in RFC 3611 section 4.4.
//
//	0                   1                   2                   3
//	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
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     BT=4      |   reserved    |       block length = 2        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |              NTP timestamp, most significant word             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |             NTP timestamp, least significant word             |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// .
type ReceiverReferenceTimeReportBlock struct {
	XRHeader
	NTPTimestamp uint64
}

// DestinationSSRC returns an array of SSRC values that this report block refers to.
func (b *ReceiverReferenceTimeReportBlock) DestinationSSRC() []uint32 {
	return []uint32{}
}

func (b *ReceiverReferenceTimeReportBlock) setupBlockHeader() {
	b.XRHeader.BlockType = ReceiverReferenceTimeReportBlockType
	b.XRHeader.TypeSpecific = 0
	b.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115
}

func (b *ReceiverReferenceTimeReportBlock) unpackBlockHeader() {
}

// DLRRReportBlock encodes a DLRR Report Block as described in
// RFC 3611 section 4.5.
//
//	0                   1                   2                   3
//	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
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     BT=5      |   reserved    |         block length          |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// |                 SSRC_1 (SSRC of first receiver)               | sub-
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block
// |                         last RR (LRR)                         |   1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                   delay since last RR (DLRR)                  |
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// |                 SSRC_2 (SSRC of second receiver)              | sub-
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block
// :                               ...                             :   2
// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
// .
type DLRRReportBlock struct {
	XRHeader
	Reports []DLRRReport
}

// DLRRReport encodes a single report inside a DLRRReportBlock.
type DLRRReport struct {
	SSRC   uint32 `fmt:"0x%X"`
	LastRR uint32
	DLRR   uint32
}

// DestinationSSRC returns an array of SSRC values that this report block refers to.
func (b *DLRRReportBlock) DestinationSSRC() []uint32 {
	ssrc := make([]uint32, len(b.Reports))
	for i, r := range b.Reports {
		ssrc[i] = r.SSRC
	}

	return ssrc
}

func (b *DLRRReportBlock) setupBlockHeader() {
	b.XRHeader.BlockType = DLRRReportBlockType
	b.XRHeader.TypeSpecific = 0
	b.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115
}

func (b *DLRRReportBlock) unpackBlockHeader() {
}

// StatisticsSummaryReportBlock encodes a Statistics Summary Report
// Block as described in RFC 3611, section 4.6.
//
//	0                   1                   2                   3
//	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
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     BT=6      |L|D|J|ToH|rsvd.|       block length = 9        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                        SSRC of source                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          begin_seq            |             end_seq           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                        lost_packets                           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                        dup_packets                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         min_jitter                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         max_jitter                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         mean_jitter                           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         dev_jitter                            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | min_ttl_or_hl | max_ttl_or_hl |mean_ttl_or_hl | dev_ttl_or_hl |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// .
type StatisticsSummaryReportBlock struct {
	XRHeader
	LossReports      bool              `encoding:"omit"`
	DuplicateReports bool              `encoding:"omit"`
	JitterReports    bool              `encoding:"omit"`
	TTLorHopLimit    TTLorHopLimitType `encoding:"omit"`
	SSRC             uint32            `fmt:"0x%X"`
	BeginSeq         uint16
	EndSeq           uint16
	LostPackets      uint32
	DupPackets       uint32
	MinJitter        uint32
	MaxJitter        uint32
	MeanJitter       uint32
	DevJitter        uint32
	MinTTLOrHL       uint8
	MaxTTLOrHL       uint8
	MeanTTLOrHL      uint8
	DevTTLOrHL       uint8
}

// TTLorHopLimitType encodes values for the ToH field in
// a StatisticsSummaryReportBlock.
type TTLorHopLimitType uint8

// Values for TTLorHopLimitType.
const (
	ToHMissing = 0
	ToHIPv4    = 1
	ToHIPv6    = 2
)

func (t TTLorHopLimitType) String() string {
	switch t {
	case ToHMissing:
		return "[ToH Missing]"
	case ToHIPv4:
		return "[ToH = IPv4]"
	case ToHIPv6:
		return "[ToH = IPv6]"
	}

	return "[ToH Flag is Invalid]"
}

// DestinationSSRC returns an array of SSRC values that this report block refers to.
func (b *StatisticsSummaryReportBlock) DestinationSSRC() []uint32 {
	return []uint32{b.SSRC}
}

func (b *StatisticsSummaryReportBlock) setupBlockHeader() {
	b.XRHeader.BlockType = StatisticsSummaryReportBlockType
	b.XRHeader.TypeSpecific = 0x00
	if b.LossReports {
		b.XRHeader.TypeSpecific |= 0x80
	}
	if b.DuplicateReports {
		b.XRHeader.TypeSpecific |= 0x40
	}
	if b.JitterReports {
		b.XRHeader.TypeSpecific |= 0x20
	}
	b.XRHeader.TypeSpecific |= TypeSpecificField((b.TTLorHopLimit & 0x03) << 3)
	b.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115
}

func (b *StatisticsSummaryReportBlock) unpackBlockHeader() {
	b.LossReports = b.XRHeader.TypeSpecific&0x80 != 0
	b.DuplicateReports = b.XRHeader.TypeSpecific&0x40 != 0
	b.JitterReports = b.XRHeader.TypeSpecific&0x20 != 0
	b.TTLorHopLimit = TTLorHopLimitType((b.XRHeader.TypeSpecific & 0x18) >> 3)
}

// VoIPMetricsReportBlock encodes a VoIP Metrics Report Block as described
// in RFC 3611, section 4.7.
//
//	0                   1                   2                   3
//	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
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     BT=7      |   reserved    |       block length = 8        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                        SSRC of source                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |   loss rate   | discard rate  | burst density |  gap density  |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |       burst duration          |         gap duration          |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |     round trip delay          |       end system delay        |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | signal level  |  noise level  |     RERL      |     Gmin      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |   R factor    | ext. R factor |    MOS-LQ     |    MOS-CQ     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |   RX config   |   reserved    |          JB nominal           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          JB maximum           |          JB abs max           |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// .
type VoIPMetricsReportBlock struct {
	XRHeader
	SSRC           uint32 `fmt:"0x%X"`
	LossRate       uint8
	DiscardRate    uint8
	BurstDensity   uint8
	GapDensity     uint8
	BurstDuration  uint16
	GapDuration    uint16
	RoundTripDelay uint16
	EndSystemDelay uint16
	SignalLevel    uint8
	NoiseLevel     uint8
	RERL           uint8
	Gmin           uint8
	RFactor        uint8
	ExtRFactor     uint8
	MOSLQ          uint8
	MOSCQ          uint8
	RXConfig       uint8
	_              uint8
	JBNominal      uint16
	JBMaximum      uint16
	JBAbsMax       uint16
}

// DestinationSSRC returns an array of SSRC values that this report block refers to.
func (b *VoIPMetricsReportBlock) DestinationSSRC() []uint32 {
	return []uint32{b.SSRC}
}

func (b *VoIPMetricsReportBlock) setupBlockHeader() {
	b.XRHeader.BlockType = VoIPMetricsReportBlockType
	b.XRHeader.TypeSpecific = 0
	b.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115
}

func (b *VoIPMetricsReportBlock) unpackBlockHeader() {
}

// UnknownReportBlock is used to store bytes for any report block
// that has an unknown Report Block Type.
type UnknownReportBlock struct {
	XRHeader
	Bytes []byte
}

// DestinationSSRC returns an array of SSRC values that this report block refers to.
func (b *UnknownReportBlock) DestinationSSRC() []uint32 {
	return []uint32{}
}

func (b *UnknownReportBlock) setupBlockHeader() {
	b.XRHeader.BlockLength = uint16(wireSize(b)/4 - 1) //nolint:gosec // G115
}

func (b *UnknownReportBlock) unpackBlockHeader() {
}

// MarshalSize returns the size of the packet once marshaled.
func (x ExtendedReport) MarshalSize() int {
	return wireSize(x)
}

// Marshal encodes the ExtendedReport in binary.
func (x ExtendedReport) Marshal() ([]byte, error) {
	for _, p := range x.Reports {
		p.setupBlockHeader()
	}

	length := wireSize(x)

	// RTCP Header
	header := Header{
		Type:   TypeExtendedReport,
		Length: uint16(length / 4), //nolint:gosec // G115
	}
	headerBuffer, err := header.Marshal()
	if err != nil {
		return []byte{}, err
	}
	length += len(headerBuffer)

	rawPacket := make([]byte, length)
	buffer := packetBuffer{bytes: rawPacket}

	err = buffer.write(headerBuffer)
	if err != nil {
		return []byte{}, err
	}
	err = buffer.write(x)
	if err != nil {
		return []byte{}, err
	}

	return rawPacket, nil
}

// Unmarshal decodes the ExtendedReport from binary.
//
//nolint:cyclop
func (x *ExtendedReport) Unmarshal(b []byte) error {
	var header Header
	if err := header.Unmarshal(b); err != nil {
		return err
	}
	if header.Type != TypeExtendedReport {
		return errWrongType
	}

	buffer := packetBuffer{bytes: b[headerLength:]}
	err := buffer.read(&x.SenderSSRC)
	if err != nil {
		return err
	}

	for len(buffer.bytes) > 0 {
		var block ReportBlock

		headerBuffer := buffer
		xrHeader := XRHeader{}
		err = headerBuffer.read(&xrHeader)
		if err != nil {
			return err
		}

		switch xrHeader.BlockType {
		case LossRLEReportBlockType:
			block = new(LossRLEReportBlock)
		case DuplicateRLEReportBlockType:
			block = new(DuplicateRLEReportBlock)
		case PacketReceiptTimesReportBlockType:
			block = new(PacketReceiptTimesReportBlock)
		case ReceiverReferenceTimeReportBlockType:
			block = new(ReceiverReferenceTimeReportBlock)
		case DLRRReportBlockType:
			block = new(DLRRReportBlock)
		case StatisticsSummaryReportBlockType:
			block = new(StatisticsSummaryReportBlock)
		case VoIPMetricsReportBlockType:
			block = new(VoIPMetricsReportBlock)
		default:
			block = new(UnknownReportBlock)
		}

		// We need to limit the amount of data available to
		// this block to the actual length of the block
		blockLength := (int(xrHeader.BlockLength) + 1) * 4
		blockBuffer := buffer.split(blockLength)
		err = blockBuffer.read(block)
		if err != nil {
			return err
		}
		block.unpackBlockHeader()
		x.Reports = append(x.Reports, block)
	}

	return nil
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (x *ExtendedReport) DestinationSSRC() []uint32 {
	ssrc := make([]uint32, 0, len(x.Reports)+1)
	ssrc = append(ssrc, x.SenderSSRC)
	for _, p := range x.Reports {
		ssrc = append(ssrc, p.DestinationSSRC()...)
	}

	return ssrc
}

func (x *ExtendedReport) String() string {
	return stringify(x)
}


================================================
FILE: extended_report_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
)

// Assert that ExtendedReport is a Packet.
var _ Packet = (*ExtendedReport)(nil)

// Assert that all the extended report blocks implement the interface.
var (
	_ ReportBlock = (*LossRLEReportBlock)(nil)
	_ ReportBlock = (*DuplicateRLEReportBlock)(nil)
	_ ReportBlock = (*PacketReceiptTimesReportBlock)(nil)
	_ ReportBlock = (*ReceiverReferenceTimeReportBlock)(nil)
	_ ReportBlock = (*DLRRReportBlock)(nil)
	_ ReportBlock = (*StatisticsSummaryReportBlock)(nil)
	_ ReportBlock = (*VoIPMetricsReportBlock)(nil)
	_ ReportBlock = (*UnknownReportBlock)(nil)
)

func testPacket() Packet {
	return &ExtendedReport{
		SenderSSRC: 0x01020304,
		Reports: []ReportBlock{
			&LossRLEReportBlock{
				XRHeader: XRHeader{
					BlockType: LossRLEReportBlockType,
				},
				T:        12,
				SSRC:     0x12345689,
				BeginSeq: 5,
				EndSeq:   12,
				Chunks: []Chunk{
					Chunk(0x4006),
					Chunk(0x0006),
					Chunk(0x8765),
					Chunk(0x0000),
				},
			},
			&DuplicateRLEReportBlock{
				XRHeader: XRHeader{
					BlockType: DuplicateRLEReportBlockType,
				},
				T:        6,
				SSRC:     0x12345689,
				BeginSeq: 5,
				EndSeq:   12,
				Chunks: []Chunk{
					Chunk(0x4123),
					Chunk(0x3FFF),
					Chunk(0xFFFF),
					Chunk(0x0000),
				},
			},
			&PacketReceiptTimesReportBlock{
				XRHeader: XRHeader{
					BlockType: PacketReceiptTimesReportBlockType,
				},
				T:        3,
				SSRC:     0x98765432,
				BeginSeq: 15432,
				EndSeq:   15577,
				ReceiptTime: []uint32{
					0x11111111,
					0x22222222,
					0x33333333,
					0x44444444,
					0x55555555,
				},
			},
			&ReceiverReferenceTimeReportBlock{
				XRHeader: XRHeader{
					BlockType: ReceiverReferenceTimeReportBlockType,
				},
				NTPTimestamp: 0x0102030405060708,
			},
			&DLRRReportBlock{
				XRHeader: XRHeader{
					BlockType: DLRRReportBlockType,
				},
				Reports: []DLRRReport{
					{
						SSRC:   0x88888888,
						LastRR: 0x12345678,
						DLRR:   0x99999999,
					},
					{
						SSRC:   0x09090909,
						LastRR: 0x12345678,
						DLRR:   0x99999999,
					},
					{
						SSRC:   0x11223344,
						LastRR: 0x12345678,
						DLRR:   0x99999999,
					},
				},
			},
			&StatisticsSummaryReportBlock{
				XRHeader{
					BlockType: StatisticsSummaryReportBlockType,
				},
				true, true, true, ToHIPv4,
				0xFEDCBA98,
				0x1234, 0x5678,
				0x11111111,
				0x22222222,
				0x33333333,
				0x44444444,
				0x55555555,
				0x66666666,
				0x01, 0x02, 0x03, 0x04,
			},
			&VoIPMetricsReportBlock{
				XRHeader{
					BlockType: VoIPMetricsReportBlockType,
				},
				0x89ABCDEF,
				0x05, 0x06, 0x07, 0x08,
				0x1111, 0x2222, 0x3333, 0x4444,
				0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
				0x00,
				0x1122, 0x3344, 0x5566,
			},
		},
	}
}

func encodedPacket() []byte {
	return []byte{
		// RTP Header
		0x80, 0xCF, 0x00, 0x33, // byte 0 - 3
		// Sender SSRC
		0x01, 0x02, 0x03, 0x04,
		// Loss RLE Report Block
		0x01, 0x0C, 0x00, 0x04, // byte 8 - 11
		// Source SSRC
		0x12, 0x34, 0x56, 0x89,
		// Begin & End Seq
		0x00, 0x05, 0x00, 0x0C, // byte 16 - 19
		// Chunks
		0x40, 0x06, 0x00, 0x06,
		0x87, 0x65, 0x00, 0x00, // byte 24 - 27
		// Duplicate RLE Report Block
		0x02, 0x06, 0x00, 0x04,
		// Source SSRC
		0x12, 0x34, 0x56, 0x89, // byte 32 - 35
		// Begin & End Seq
		0x00, 0x05, 0x00, 0x0C,
		// Chunks
		0x41, 0x23, 0x3F, 0xFF, // byte 40 - 43
		0xFF, 0xFF, 0x00, 0x00,
		// Packet Receipt Times Report Block
		0x03, 0x03, 0x00, 0x07, // byte 48 - 51
		// Source SSRC
		0x98, 0x76, 0x54, 0x32,
		// Begin & End Seq
		0x3C, 0x48, 0x3C, 0xD9, // byte 56 - 59
		// Receipt times
		0x11, 0x11, 0x11, 0x11,
		0x22, 0x22, 0x22, 0x22, // byte 64 - 67
		0x33, 0x33, 0x33, 0x33,
		0x44, 0x44, 0x44, 0x44, // byte 72 - 75
		0x55, 0x55, 0x55, 0x55,
		// Receiver Reference Time Report
		0x04, 0x00, 0x00, 0x02, // byte 80 - 83
		// Timestamp
		0x01, 0x02, 0x03, 0x04,
		0x05, 0x06, 0x07, 0x08, // byte 88 - 91
		// DLRR Report
		0x05, 0x00, 0x00, 0x09,
		// SSRC 1
		0x88, 0x88, 0x88, 0x88, // byte 96 - 99
		// LastRR 1
		0x12, 0x34, 0x56, 0x78,
		// DLRR 1
		0x99, 0x99, 0x99, 0x99, // byte 104 - 107
		// SSRC 2
		0x09, 0x09, 0x09, 0x09,
		// LastRR 2
		0x12, 0x34, 0x56, 0x78, // byte 112 - 115
		// DLRR 2
		0x99, 0x99, 0x99, 0x99,
		// SSRC 3
		0x11, 0x22, 0x33, 0x44, // byte 120 - 123
		// LastRR 3
		0x12, 0x34, 0x56, 0x78,
		// DLRR 3
		0x99, 0x99, 0x99, 0x99, // byte 128 - 131
		// Statistics Summary Report
		0x06, 0xE8, 0x00, 0x09,
		// SSRC
		0xFE, 0xDC, 0xBA, 0x98, // byte 136 - 139
		// Various statistics
		0x12, 0x34, 0x56, 0x78,
		0x11, 0x11, 0x11, 0x11, // byte 144 - 147
		0x22, 0x22, 0x22, 0x22,
		0x33, 0x33, 0x33, 0x33, // byte 152 - 155
		0x44, 0x44, 0x44, 0x44,
		0x55, 0x55, 0x55, 0x55, // byte 160 - 163
		0x66, 0x66, 0x66, 0x66,
		0x01, 0x02, 0x03, 0x04, // byte 168 - 171
		// VoIP Metrics Report
		0x07, 0x00, 0x00, 0x08,
		// SSRC
		0x89, 0xAB, 0xCD, 0xEF, // byte 176 - 179
		// Various statistics
		0x05, 0x06, 0x07, 0x08,
		0x11, 0x11, 0x22, 0x22, // byte 184 - 187
		0x33, 0x33, 0x44, 0x44,
		0x11, 0x22, 0x33, 0x44, // byte 192 - 195
		0x55, 0x66, 0x77, 0x88,
		0x99, 0x00, 0x11, 0x22, // byte 200 - 203
		0x33, 0x44, 0x55, 0x66, // byte 204 - 207
	}
}

func TestEncode(t *testing.T) {
	expected := encodedPacket()
	packet := testPacket()
	rawPacket, err := packet.Marshal()
	assert.NoError(t, err)
	assert.Equal(t, len(expected), len(rawPacket), "Encoded message length does not match expected length")

	for i := range rawPacket {
		assert.Equalf(t, expected[i], rawPacket[i], "Byte %d of encoded packet does not match", i)
	}
}

func TestDecode(t *testing.T) {
	encoded := encodedPacket()
	expected := testPacket()

	// We need to make sure the header has been set up correctly
	// before we test for equality
	extendedReports, ok := expected.(*ExtendedReport)
	assert.True(t, ok)

	for _, p := range extendedReports.Reports {
		p.setupBlockHeader()
	}

	report := new(ExtendedReport)
	err := report.Unmarshal(encoded)
	assert.NoError(t, err)
	assert.Equal(t, expected, report)

	pktStringer, ok := expected.(fmt.Stringer)
	assert.True(t, ok)
	assert.Equal(t, report.String(), pktStringer.String(), "Decoded packet does not match expected packet")

	var includeSenderSSRC bool
	for _, ssrc := range report.DestinationSSRC() {
		if ssrc == report.SenderSSRC {
			includeSenderSSRC = true
		}
	}
	assert.True(t, includeSenderSSRC, "DestinationSSRC does not include the SenderSSRC")
}


================================================
FILE: full_intra_request.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"encoding/binary"
	"fmt"
	"strings"
)

// A FIREntry is a (SSRC, seqno) pair, as carried by FullIntraRequest.
type FIREntry struct {
	SSRC           uint32
	SequenceNumber uint8
}

// The FullIntraRequest packet is used to reliably request an Intra frame
// in a video stream.  See RFC 5104 Section 3.5.1.  This is not for loss
// recovery, which should use PictureLossIndication (PLI) instead.
type FullIntraRequest struct {
	SenderSSRC uint32
	MediaSSRC  uint32

	FIR []FIREntry
}

const (
	firOffset = 8
)

var _ Packet = (*FullIntraRequest)(nil)

// Marshal encodes the FullIntraRequest.
func (p FullIntraRequest) Marshal() ([]byte, error) {
	rawPacket := make([]byte, firOffset+(len(p.FIR)*8))
	binary.BigEndian.PutUint32(rawPacket, p.SenderSSRC)
	binary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC)
	for i, fir := range p.FIR {
		binary.BigEndian.PutUint32(rawPacket[firOffset+8*i:], fir.SSRC)
		rawPacket[firOffset+8*i+4] = fir.SequenceNumber
	}
	h := p.Header()
	hData, err := h.Marshal()
	if err != nil {
		return nil, err
	}

	return append(hData, rawPacket...), nil
}

// Unmarshal decodes the TransportLayerNack.
func (p *FullIntraRequest) Unmarshal(rawPacket []byte) error {
	if len(rawPacket) < (headerLength + ssrcLength) {
		return errPacketTooShort
	}

	var header Header
	if err := header.Unmarshal(rawPacket); err != nil {
		return err
	}

	if len(rawPacket) < (headerLength + int(4*header.Length)) {
		return errPacketTooShort
	}

	if header.Type != TypePayloadSpecificFeedback || header.Count != FormatFIR {
		return errWrongType
	}

	// The FCI field MUST contain one or more FIR entries
	if 4*header.Length-firOffset <= 0 || (4*header.Length)%8 != 0 {
		return errBadLength
	}

	p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
	p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
	for i := headerLength + firOffset; i < (headerLength + int(header.Length*4)); i += 8 {
		p.FIR = append(p.FIR, FIREntry{
			binary.BigEndian.Uint32(rawPacket[i:]),
			rawPacket[i+4],
		})
	}

	return nil
}

// Header returns the Header associated with this packet.
func (p *FullIntraRequest) Header() Header {
	return Header{
		Count:  FormatFIR,
		Type:   TypePayloadSpecificFeedback,
		Length: uint16((p.MarshalSize() / 4) - 1), //nolint:gosec // G115
	}
}

// MarshalSize returns the size of the packet once marshaled.
func (p *FullIntraRequest) MarshalSize() int {
	return headerLength + firOffset + len(p.FIR)*8
}

func (p *FullIntraRequest) String() string {
	var out strings.Builder
	fmt.Fprintf(&out, "FullIntraRequest %x %x",
		p.SenderSSRC, p.MediaSSRC)
	for _, e := range p.FIR {
		fmt.Fprintf(&out, " (%x %v)", e.SSRC, e.SequenceNumber)
	}

	return out.String()
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *FullIntraRequest) DestinationSSRC() []uint32 {
	ssrcs := make([]uint32, 0, len(p.FIR))
	for _, entry := range p.FIR {
		ssrcs = append(ssrcs, entry.SSRC)
	}

	return ssrcs
}


================================================
FILE: full_intra_request_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestFullIntraRequestUnmarshal(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Data      []byte
		Want      FullIntraRequest
		WantError error
	}{
		{
			Name: "valid",
			Data: []byte{
				// v=2, p=0, FMT=4, PSFB, len=4
				0x84, 0xce, 0x00, 0x04,
				// ssrc=0x0
				0x00, 0x00, 0x00, 0x00,
				// ssrc=0x4bc4fcb4
				0x4b, 0xc4, 0xfc, 0xb4,
				// ssrc=0x12345678
				0x12, 0x34, 0x56, 0x78,
				// Seqno=0x42
				0x42, 0x00, 0x00, 0x00,
			},
			Want: FullIntraRequest{
				SenderSSRC: 0x0,
				MediaSSRC:  0x4bc4fcb4,
				FIR: []FIREntry{
					{
						SSRC:           0x12345678,
						SequenceNumber: 0x42,
					},
				},
			},
		},
		{
			Name: "also valid",
			Data: []byte{
				// v=2, p=0, FMT=4, PSFB, len=6
				0x84, 0xce, 0x00, 0x06,
				// ssrc=0x0
				0x00, 0x00, 0x00, 0x00,
				// ssrc=0x4bc4fcb4
				0x4b, 0xc4, 0xfc, 0xb4,
				// ssrc=0x12345678
				0x12, 0x34, 0x56, 0x78,
				// Seqno=0x42
				0x42, 0x00, 0x00, 0x00,
				// ssrc=0x98765432
				0x98, 0x76, 0x54, 0x32,
				// Seqno=0x57
				0x57, 0x00, 0x00, 0x00,
			},
			Want: FullIntraRequest{
				SenderSSRC: 0x0,
				MediaSSRC:  0x4bc4fcb4,
				FIR: []FIREntry{
					{
						SSRC:           0x12345678,
						SequenceNumber: 0x42,
					},
					{
						SSRC:           0x98765432,
						SequenceNumber: 0x57,
					},
				},
			},
		},
		{
			Name: "packet too short",
			Data: []byte{
				0x00, 0x00, 0x00, 0x00,
			},
			WantError: errPacketTooShort,
		},
		{
			Name: "invalid header",
			Data: []byte{
				0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00,
			},
			WantError: errBadVersion,
		},
		{
			Name: "wrong type",
			Data: []byte{
				// v=2, p=0, FMT=4, RR, len=4
				0x84, 0xc9, 0x00, 0x04,
				// ssrc=0x0
				0x00, 0x00, 0x00, 0x00,
				// ssrc=0x4bc4fcb4
				0x4b, 0xc4, 0xfc, 0xb4,
				// ssrc=0x12345678
				0x12, 0x34, 0x56, 0x78,
				// Seqno=0x42
				0x42, 0x00, 0x00, 0x00,
			},
			WantError: errWrongType,
		},
		{
			Name: "wrong fmt",
			Data: []byte{
				// v=2, p=0, FMT=2, PSFB, len=4
				0x82, 0xce, 0x00, 0x04,
				// ssrc=0x0
				0x00, 0x00, 0x00, 0x00,
				// ssrc=0x4bc4fcb4
				0x4b, 0xc4, 0xfc, 0xb4,
				// ssrc=0x12345678
				0x12, 0x34, 0x56, 0x78,
				// Seqno=0x42
				0x42, 0x00, 0x00, 0x00,
			},
			WantError: errWrongType,
		},
		{
			Name: "wrong length",
			Data: []byte{
				// v=2, p=0, FMT=4, PSFB, len=3
				0x84, 0xce, 0x00, 0x03,
				// ssrc=0x0
				0x00, 0x00, 0x00, 0x00,
				// ssrc=0x4bc4fcb4
				0x4b, 0xc4, 0xfc, 0xb4,
				// ssrc=0x12345678
				0x12, 0x34, 0x56, 0x78,
			},
			WantError: errBadLength,
		},
	} {
		var fir FullIntraRequest
		err := fir.Unmarshal(test.Data)
		assert.ErrorIsf(t, err, test.WantError, "Unmarshal %q rr mismatch", test.Name)
		if err != nil {
			continue
		}

		assert.Equalf(t, test.Want, fir, "Unmarshal %q rr mismatch", test.Name)
	}
}

func TestFullIntraRequestRoundTrip(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Packet    FullIntraRequest
		WantError error
	}{
		{
			Name: "valid",
			Packet: FullIntraRequest{
				SenderSSRC: 1,
				MediaSSRC:  2,
				FIR: []FIREntry{{
					SSRC:           3,
					SequenceNumber: 42,
				}},
			},
		},
		{
			Name: "also valid",
			Packet: FullIntraRequest{
				SenderSSRC: 5000,
				MediaSSRC:  6000,
				FIR: []FIREntry{{
					SSRC:           3,
					SequenceNumber: 57,
				}},
			},
		},
	} {
		data, err := test.Packet.Marshal()
		assert.ErrorIsf(t, err, test.WantError, "Marshal %q", test.Name)
		if err != nil {
			continue
		}

		var decoded FullIntraRequest
		assert.NoErrorf(t, decoded.Unmarshal(data), "Unmarshal %q", test.Name)
		assert.Equalf(t, test.Packet, decoded, "%q rr header mismatch", test.Name)
	}
}

func TestFullIntraRequestUnmarshalHeader(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Data      []byte
		Want      Header
		WantError error
	}{
		{
			Name: "valid header",
			Data: []byte{
				// v=2, p=0, FMT=1, PSFB, len=4
				0x84, 0xce, 0x00, 0x04,
				// ssrc=0x0
				0x00, 0x00, 0x00, 0x00,
				// ssrc=0x4bc4fcb4
				0x4b, 0xc4, 0xfc, 0xb4,
				// ssrc=0x00000000
				0x00, 0x00, 0x00, 0x00,
				// Seqno=0x22
				0x22, 0x00, 0x00, 0x00,
			},
			Want: Header{
				Count:  FormatFIR,
				Type:   TypePayloadSpecificFeedback,
				Length: 4,
			},
		},
	} {
		var fir FullIntraRequest
		err := fir.Unmarshal(test.Data)
		assert.ErrorIsf(t, err, test.WantError, "Unmarshal header %q rr mismatch", test.Name)
		if err != nil {
			continue
		}

		assert.Equalf(t, test.Want, fir.Header(), "Unmarshal header %q rr mismatch", test.Name)
	}
}


================================================
FILE: fuzz_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"
)

func FuzzUnmarshal(f *testing.F) {
	f.Add([]byte{})

	f.Fuzz(func(_ *testing.T, data []byte) {
		packets, err := Unmarshal(data)
		if err != nil {
			return
		}

		for _, packet := range packets {
			_, err = packet.Marshal()
			if err != nil {
				return
			}
		}
	})
}


================================================
FILE: go.mod
================================================
module github.com/pion/rtcp

go 1.24.0

require github.com/stretchr/testify v1.11.1

require (
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: goodbye.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"encoding/binary"
	"fmt"
	"strings"
)

// The Goodbye packet indicates that one or more sources are no longer active.
type Goodbye struct {
	// The SSRC/CSRC identifiers that are no longer active
	Sources []uint32
	// Optional text indicating the reason for leaving, e.g., "camera malfunction" or "RTP loop detected"
	Reason string
}

// Marshal encodes the Goodbye packet in binary.
func (g Goodbye) Marshal() ([]byte, error) {
	/*
	 *        0                   1                   2                   3
	 *        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
	 *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *       |V=2|P|    SC   |   PT=BYE=203  |             length            |
	 *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *       |                           SSRC/CSRC                           |
	 *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *       :                              ...                              :
	 *       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * (opt) |     length    |               reason for leaving            ...
	 *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	rawPacket := make([]byte, g.MarshalSize())
	packetBody := rawPacket[headerLength:]

	if len(g.Sources) > countMax {
		return nil, errTooManySources
	}

	for i, s := range g.Sources {
		binary.BigEndian.PutUint32(packetBody[i*ssrcLength:], s)
	}

	if g.Reason != "" {
		reason := []byte(g.Reason)

		if len(reason) > sdesMaxOctetCount {
			return nil, errReasonTooLong
		}

		reasonOffset := len(g.Sources) * ssrcLength
		packetBody[reasonOffset] = uint8(len(reason)) //nolint:gosec // G115
		copy(packetBody[reasonOffset+1:], reason)
	}

	hData, err := g.Header().Marshal()
	if err != nil {
		return nil, err
	}
	copy(rawPacket, hData)

	return rawPacket, nil
}

// Unmarshal decodes the Goodbye packet from binary.
func (g *Goodbye) Unmarshal(rawPacket []byte) error {
	/*
	 *        0                   1                   2                   3
	 *        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
	 *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *       |V=2|P|    SC   |   PT=BYE=203  |             length            |
	 *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *       |                           SSRC/CSRC                           |
	 *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *       :                              ...                              :
	 *       +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * (opt) |     length    |               reason for leaving            ...
	 *       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	var header Header
	if err := header.Unmarshal(rawPacket); err != nil {
		return err
	}

	if header.Type != TypeGoodbye {
		return errWrongType
	}

	if getPadding(len(rawPacket)) != 0 {
		return errPacketTooShort
	}

	g.Sources = make([]uint32, header.Count)

	reasonOffset := int(headerLength + header.Count*ssrcLength)
	if reasonOffset > len(rawPacket) {
		return errPacketTooShort
	}

	for i := 0; i < int(header.Count); i++ {
		offset := headerLength + i*ssrcLength

		g.Sources[i] = binary.BigEndian.Uint32(rawPacket[offset:])
	}

	if reasonOffset < len(rawPacket) {
		reasonLen := int(rawPacket[reasonOffset])
		reasonEnd := reasonOffset + 1 + reasonLen

		if reasonEnd > len(rawPacket) {
			return errPacketTooShort
		}

		g.Reason = string(rawPacket[reasonOffset+1 : reasonEnd])
	}

	return nil
}

// Header returns the Header associated with this packet.
func (g *Goodbye) Header() Header {
	return Header{
		Padding: false,
		Count:   uint8(len(g.Sources)), //nolint:gosec //G115
		Type:    TypeGoodbye,
		Length:  uint16((g.MarshalSize() / 4) - 1), //nolint:gosec //G115
	}
}

// MarshalSize returns the size of the packet once marshaled.
func (g *Goodbye) MarshalSize() int {
	srcsLength := len(g.Sources) * ssrcLength
	// reason is optional
	reasonLength := len(g.Reason)
	if reasonLength > 0 {
		reasonLength++
	}

	l := headerLength + srcsLength + reasonLength

	// align to 32-bit boundary
	return l + getPadding(l)
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (g *Goodbye) DestinationSSRC() []uint32 {
	out := make([]uint32, len(g.Sources))
	copy(out, g.Sources)

	return out
}

func (g Goodbye) String() string {
	var out strings.Builder
	out.WriteString("Goodbye\n")
	for i, s := range g.Sources {
		fmt.Fprintf(&out, "\tSource %d: %x\n", i, s)
	}
	fmt.Fprintf(&out, "\tReason: %s\n", g.Reason)

	return out.String()
}


================================================
FILE: goodbye_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

var _ Packet = (*Goodbye)(nil) // assert is a Packet

func TestGoodbyeUnmarshal(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Data      []byte
		Want      Goodbye
		WantError error
	}{
		{
			Name: "valid",
			Data: []byte{
				// v=2, p=0, count=1, BYE, len=12
				0x81, 0xcb, 0x00, 0x0c,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// len=3, text=FOO
				0x03, 0x46, 0x4f, 0x4f,
			},
			Want: Goodbye{
				Sources: []uint32{0x902f9e2e},
				Reason:  "FOO",
			},
		},
		{
			Name: "invalid octet count",
			Data: []byte{
				// v=2, p=0, count=1, BYE, len=12
				0x81, 0xcb, 0x00, 0x0c,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// len=4, text=FOO
				0x04, 0x46, 0x4f, 0x4f,
			},
			WantError: errPacketTooShort,
		},
		{
			Name: "wrong type",
			Data: []byte{
				// v=2, p=0, count=1, SDES, len=12
				0x81, 0xca, 0x00, 0x0c,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// len=3, text=FOO
				0x03, 0x46, 0x4f, 0x4f,
			},
			WantError: errWrongType,
		},
		{
			Name: "short reason",
			Data: []byte{
				// v=2, p=0, count=1, BYE, len=12
				0x81, 0xcb, 0x00, 0x0c,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// len=3, text=F + padding
				0x01, 0x46, 0x00, 0x00,
			},
			Want: Goodbye{
				Sources: []uint32{0x902f9e2e},
				Reason:  "F",
			},
		},
		{
			Name: "not byte aligned",
			Data: []byte{
				// v=2, p=0, count=1, BYE, len=10
				0x81, 0xcb, 0x00, 0x0a,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// len=1, text=F
				0x01, 0x46,
			},
			WantError: errPacketTooShort,
		},
		{
			Name: "bad count in header",
			Data: []byte{
				// v=2, p=0, count=2, BYE, len=8
				0x82, 0xcb, 0x00, 0x0c,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
			},
			WantError: errPacketTooShort,
		},
		{
			Name: "empty packet",
			Data: []byte{
				// v=2, p=0, count=0, BYE, len=4
				0x80, 0xcb, 0x00, 0x04,
			},
			Want: Goodbye{
				Sources: []uint32{},
				Reason:  "",
			},
		},
		{
			Name:      "nil",
			Data:      nil,
			WantError: errPacketTooShort,
		},
	} {
		var bye Goodbye
		err := bye.Unmarshal(test.Data)
		assert.ErrorIsf(t, err, test.WantError, "Unmarshal %q bye mismatch", test.Name)
		if err != nil {
			continue
		}

		assert.Equalf(t, test.Want, bye, "Unmarshal %q bye mismatch", test.Name)
	}
}

func TestGoodbyeRoundTrip(t *testing.T) {
	// a slice with enough sources to overflow an 5-bit int
	var tooManySources []uint32
	var tooLongText strings.Builder

	for range 1 << 5 {
		tooManySources = append(tooManySources, 0x00)
	}
	for range 1 << 8 {
		tooLongText.WriteString("x")
	}

	for _, test := range []struct {
		Name      string
		Bye       Goodbye
		WantError error
	}{
		{
			Name: "empty",
			Bye: Goodbye{
				Sources: []uint32{},
			},
		},
		{
			Name: "valid",
			Bye: Goodbye{
				Sources: []uint32{
					0x01020304,
					0x05060708,
				},
				Reason: "because",
			},
		},
		{
			Name: "empty reason",
			Bye: Goodbye{
				Sources: []uint32{0x01020304},
				Reason:  "",
			},
		},
		{
			Name: "reason no source",
			Bye: Goodbye{
				Sources: []uint32{},
				Reason:  "foo",
			},
		},
		{
			Name: "short reason",
			Bye: Goodbye{
				Sources: []uint32{},
				Reason:  "f",
			},
		},
		{
			Name: "count overflow",
			Bye: Goodbye{
				Sources: tooManySources,
			},
			WantError: errTooManySources,
		},
		{
			Name: "reason too long",
			Bye: Goodbye{
				Sources: []uint32{},
				Reason:  tooLongText.String(),
			},
			WantError: errReasonTooLong,
		},
	} {
		data, err := test.Bye.Marshal()
		assert.ErrorIsf(t, err, test.WantError, "Marshal %q", test.Name)
		if err != nil {
			continue
		}

		var bye Goodbye
		assert.NoErrorf(t, bye.Unmarshal(data), "Unmarshal %q", test.Name)
		assert.Equalf(t, test.Bye, bye, "%q bye round trip mismatch", test.Name)
	}
}


================================================
FILE: header.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"encoding/binary"
)

// PacketType specifies the type of an RTCP packet.
type PacketType uint8

// RTCP packet types registered with IANA. See:
//
//	https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-4
const (
	TypeSenderReport              PacketType = 200 // RFC 3550, 6.4.1
	TypeReceiverReport            PacketType = 201 // RFC 3550, 6.4.2
	TypeSourceDescription         PacketType = 202 // RFC 3550, 6.5
	TypeGoodbye                   PacketType = 203 // RFC 3550, 6.6
	TypeApplicationDefined        PacketType = 204 // RFC 3550, 6.7 (unimplemented)
	TypeTransportSpecificFeedback PacketType = 205 // RFC 4585, 6051
	TypePayloadSpecificFeedback   PacketType = 206 // RFC 4585, 6.3
	TypeExtendedReport            PacketType = 207 // RFC 3611

)

// Transport and Payload specific feedback messages overload the count field to act as a message type.
// those are listed here.
const (
	FormatSLI  uint8 = 2
	FormatPLI  uint8 = 1
	FormatFIR  uint8 = 4
	FormatTLN  uint8 = 1
	FormatRRR  uint8 = 5
	FormatCCFB uint8 = 11
	FormatREMB uint8 = 15

	// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5
	FormatTCC uint8 = 15
)

func (p PacketType) String() string {
	switch p {
	case TypeSenderReport:
		return "SR"
	case TypeReceiverReport:
		return "RR"
	case TypeSourceDescription:
		return "SDES"
	case TypeGoodbye:
		return "BYE"
	case TypeApplicationDefined:
		return "APP"
	case TypeTransportSpecificFeedback:
		return "TSFB"
	case TypePayloadSpecificFeedback:
		return "PSFB"
	case TypeExtendedReport:
		return "XR"
	default:
		return string(p)
	}
}

const rtpVersion = 2

// A Header is the common header shared by all RTCP packets.
type Header struct {
	// If the padding bit is set, this individual RTCP packet contains
	// some additional padding octets at the end which are not part of
	// the control information but are included in the length field.
	Padding bool
	// The number of reception reports, sources contained or FMT in this packet (depending on the Type)
	Count uint8
	// The RTCP packet type for this packet
	Type PacketType
	// The length of this RTCP packet in 32-bit words minus one,
	// including the header and any padding.
	Length uint16
}

const (
	headerLength = 4
	versionShift = 6
	versionMask  = 0x3
	paddingShift = 5
	paddingMask  = 0x1
	countShift   = 0
	countMask    = 0x1f
	countMax     = (1 << 5) - 1
)

// Marshal encodes the Header in binary.
func (h Header) Marshal() ([]byte, error) {
	/*
	 *  0                   1                   2                   3
	 *  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
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * |V=2|P|    RC   |   PT=SR=200   |             length            |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */
	rawPacket := make([]byte, headerLength)

	rawPacket[0] |= rtpVersion << versionShift

	if h.Padding {
		rawPacket[0] |= 1 << paddingShift
	}

	if h.Count > 31 {
		return nil, errInvalidHeader
	}
	rawPacket[0] |= h.Count << countShift //nolint:gosec // rawPacket is created with length headerLength (4)

	rawPacket[1] = uint8(h.Type) //nolint:gosec // rawPacket is created with length headerLength (4)

	binary.BigEndian.PutUint16(rawPacket[2:], h.Length)

	return rawPacket, nil
}

// Unmarshal decodes the Header from binary.
func (h *Header) Unmarshal(rawPacket []byte) error {
	if len(rawPacket) < headerLength {
		return errPacketTooShort
	}

	/*
	 *  0                   1                   2                   3
	 *  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
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * |V=2|P|    RC   |      PT       |             length            |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	version := rawPacket[0] >> versionShift & versionMask
	if version != rtpVersion {
		return errBadVersion
	}

	h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0
	h.Count = rawPacket[0] >> countShift & countMask

	h.Type = PacketType(rawPacket[1])

	h.Length = binary.BigEndian.Uint16(rawPacket[2:])

	return nil
}


================================================
FILE: header_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestHeaderUnmarshal(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Data      []byte
		Want      Header
		WantError error
	}{
		{
			Name: "valid",
			Data: []byte{
				// v=2, p=0, count=1, RR, len=7
				0x81, 0xc9, 0x00, 0x07,
			},
			Want: Header{
				Padding: false,
				Count:   1,
				Type:    TypeReceiverReport,
				Length:  7,
			},
		},
		{
			Name: "also valid",
			Data: []byte{
				// v=2, p=1, count=1, BYE, len=7
				0xa1, 0xcc, 0x00, 0x07,
			},
			Want: Header{
				Padding: true,
				Count:   1,
				Type:    TypeApplicationDefined,
				Length:  7,
			},
		},
		{
			Name: "bad version",
			Data: []byte{
				// v=0, p=0, count=0, RR, len=4
				0x00, 0xc9, 0x00, 0x04,
			},
			WantError: errBadVersion,
		},
	} {
		var h Header
		err := h.Unmarshal(test.Data)
		assert.ErrorIsf(t, err, test.WantError, "Unmarshal %q header mispmatch", test.Name)
		if err != nil {
			continue
		}

		assert.Equalf(t, test.Want, h, "Unmarshal %q header mismatch", test.Name)
	}
}

func TestHeaderRoundTrip(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Header    Header
		WantError error
	}{
		{
			Name: "valid",
			Header: Header{
				Padding: true,
				Count:   31,
				Type:    TypeSenderReport,
				Length:  4,
			},
		},
		{
			Name: "also valid",
			Header: Header{
				Padding: false,
				Count:   28,
				Type:    TypeReceiverReport,
				Length:  65535,
			},
		},
		{
			Name: "invalid count",
			Header: Header{
				Count: 40,
			},
			WantError: errInvalidHeader,
		},
	} {
		data, err := test.Header.Marshal()
		assert.ErrorIsf(t, err, test.WantError, "Marshal %q", test.Name)
		if err != nil {
			continue
		}

		var decoded Header
		assert.NoErrorf(t, decoded.Unmarshal(data), "Unmarshal %q", test.Name)
		assert.Equalf(t, test.Header, decoded, "%q header round trip mismatch", test.Name)
	}
}


================================================
FILE: packet.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

// Packet represents an RTCP packet, a protocol used for out-of-band statistics
// and control information for an RTP session.
type Packet interface {
	// DestinationSSRC returns an array of SSRC values that this packet refers to.
	DestinationSSRC() []uint32

	Marshal() ([]byte, error)
	Unmarshal(rawPacket []byte) error
	MarshalSize() int
}

// Unmarshal takes an entire udp datagram (which may consist of multiple RTCP packets) and
// returns the unmarshaled packets it contains.
//
// If this is a reduced-size RTCP packet a feedback packet (Goodbye, SliceLossIndication, etc)
// will be returned. Otherwise, the underlying type of the returned packet will be
// CompoundPacket.
func Unmarshal(rawData []byte) ([]Packet, error) {
	var packets []Packet
	for len(rawData) != 0 {
		p, processed, err := unmarshal(rawData)
		if err != nil {
			return nil, err
		}

		packets = append(packets, p)
		rawData = rawData[processed:]
	}

	switch len(packets) {
	// Empty packet
	case 0:
		return nil, errInvalidHeader
	// Multiple Packets
	default:
		return packets, nil
	}
}

// Marshal takes an array of Packets and serializes them to a single buffer.
func Marshal(packets []Packet) ([]byte, error) {
	out := make([]byte, 0)
	for _, p := range packets {
		data, err := p.Marshal()
		if err != nil {
			return nil, err
		}
		out = append(out, data...)
	}

	return out, nil
}

// unmarshal is a factory which pulls the first RTCP packet from a bytestream,
// and returns it's parsed representation, and the amount of data that was processed.
//
//nolint:cyclop
func unmarshal(rawData []byte) (packet Packet, bytesprocessed int, err error) {
	var header Header

	err = header.Unmarshal(rawData)
	if err != nil {
		return nil, 0, err
	}

	bytesprocessed = int(header.Length+1) * 4
	if bytesprocessed > len(rawData) {
		return nil, 0, errPacketTooShort
	}
	inPacket := rawData[:bytesprocessed]

	switch header.Type {
	case TypeSenderReport:
		packet = new(SenderReport)

	case TypeReceiverReport:
		packet = new(ReceiverReport)

	case TypeSourceDescription:
		packet = new(SourceDescription)

	case TypeGoodbye:
		packet = new(Goodbye)

	case TypeTransportSpecificFeedback:
		switch header.Count {
		case FormatTLN:
			packet = new(TransportLayerNack)
		case FormatRRR:
			packet = new(RapidResynchronizationRequest)
		case FormatTCC:
			packet = new(TransportLayerCC)
		case FormatCCFB:
			packet = new(CCFeedbackReport)
		default:
			packet = new(RawPacket)
		}

	case TypePayloadSpecificFeedback:
		switch header.Count {
		case FormatPLI:
			packet = new(PictureLossIndication)
		case FormatSLI:
			packet = new(SliceLossIndication)
		case FormatREMB:
			packet = new(ReceiverEstimatedMaximumBitrate)
		case FormatFIR:
			packet = new(FullIntraRequest)
		default:
			packet = new(RawPacket)
		}

	case TypeExtendedReport:
		packet = new(ExtendedReport)

	case TypeApplicationDefined:
		packet = new(ApplicationDefined)

	default:
		packet = new(RawPacket)
	}

	err = packet.Unmarshal(inPacket)

	return packet, bytesprocessed, err
}


================================================
FILE: packet_buffer.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"encoding/binary"
	"reflect"
	"unsafe"
)

// These functions implement an introspective structure
// serializer/deserializer, designed to allow RTCP packet
// Structs to be self-describing. They currently work with
// fields of type uint8, uint16, uint32, and uint64 (and
// types derived from them).
//
// - Unexported fields will take up space in the encoded
//   array, but wil be set to zero when written, and ignore
//   when read.
//
// - Fields that are marked with the tag `encoding:"omit"`
//   will be ignored when reading and writing data.
//
// For example:
//
//   type Example struct {
//     A uint32
//     B bool   `encoding:"omit"`
//     _ uint64
//     C uint16
//   }
//
// "A" will be encoded as four bytes, in network order. "B"
// will not be encoded at all. The anonymous uint64 will
// encode as 8 bytes of value "0", followed by two bytes
// encoding "C" in network order.

type packetBuffer struct {
	bytes []byte
}

const omit = "omit"

// Writes the structure passed to into the buffer that
// PacketBuffer is initialized with. This function will
// modify the PacketBuffer.bytes slice to exclude those
// bytes that have been written into.
//
//nolint:gocognit,cyclop
func (b *packetBuffer) write(v any) error {
	value := reflect.ValueOf(v)

	// Indirect is safe to call on non-pointers, and
	// will simply return the same value in such cases
	value = reflect.Indirect(value)

	switch value.Kind() {
	case reflect.Uint8:
		if len(b.bytes) < 1 {
			return errWrongMarshalSize
		}
		if value.CanInterface() {
			b.bytes[0] = byte(value.Uint()) //nolint:gosec //  value.Kind() == reflect.Uint8 guarantees range
		}
		b.bytes = b.bytes[1:]
	case reflect.Uint16:
		if len(b.bytes) < 2 {
			return errWrongMarshalSize
		}
		if value.CanInterface() {
			binary.BigEndian.PutUint16(b.bytes, uint16(value.Uint())) //nolint:gosec // G115
		}
		b.bytes = b.bytes[2:]
	case reflect.Uint32:
		if len(b.bytes) < 4 {
			return errWrongMarshalSize
		}
		if value.CanInterface() {
			binary.BigEndian.PutUint32(b.bytes, uint32(value.Uint())) //nolint:gosec // G115
		}
		b.bytes = b.bytes[4:]
	case reflect.Uint64:
		if len(b.bytes) < 8 {
			return errWrongMarshalSize
		}
		if value.CanInterface() {
			binary.BigEndian.PutUint64(b.bytes, value.Uint())
		}
		b.bytes = b.bytes[8:]
	case reflect.Slice:
		for i := 0; i < value.Len(); i++ {
			if value.Index(i).CanInterface() {
				if err := b.write(value.Index(i).Interface()); err != nil {
					return err
				}
			} else {
				b.bytes = b.bytes[value.Index(i).Type().Size():]
			}
		}
	case reflect.Struct:
		for i := 0; i < value.NumField(); i++ {
			encoding := value.Type().Field(i).Tag.Get("encoding")
			if encoding == omit {
				continue
			}
			if value.Field(i).CanInterface() {
				if err := b.write(value.Field(i).Interface()); err != nil {
					return err
				}
			} else {
				advance := int(value.Field(i).Type().Size()) //nolint:gosec // RTCP struct field sizes are small and controlled
				if len(b.bytes) < advance {
					return errWrongMarshalSize
				}
				b.bytes = b.bytes[advance:]
			}
		}
	default:
		return errBadStructMemberType
	}

	return nil
}

// Reads bytes from the buffer as necessary to populate
// the structure passed as a parameter. This function will
// modify the PacketBuffer.bytes slice to exclude those
// bytes that have already been read.
//
//nolint:gocognit,cyclop
func (b *packetBuffer) read(v any) error {
	ptr := reflect.ValueOf(v)
	if ptr.Kind() != reflect.Ptr {
		return errBadReadParameter
	}
	value := reflect.Indirect(ptr)

	// If this is an interface, we need to make it concrete before using it
	if value.Kind() == reflect.Interface {
		value = reflect.ValueOf(value.Interface())
	}
	value = reflect.Indirect(value)

	switch value.Kind() {
	case reflect.Uint8:
		if len(b.bytes) < 1 {
			return errWrongMarshalSize
		}
		value.SetUint(uint64(b.bytes[0]))
		b.bytes = b.bytes[1:]

	case reflect.Uint16:
		if len(b.bytes) < 2 {
			return errWrongMarshalSize
		}
		value.SetUint(uint64(binary.BigEndian.Uint16(b.bytes)))
		b.bytes = b.bytes[2:]

	case reflect.Uint32:
		if len(b.bytes) < 4 {
			return errWrongMarshalSize
		}
		value.SetUint(uint64(binary.BigEndian.Uint32(b.bytes)))
		b.bytes = b.bytes[4:]

	case reflect.Uint64:
		if len(b.bytes) < 8 {
			return errWrongMarshalSize
		}
		value.SetUint(binary.BigEndian.Uint64(b.bytes))
		b.bytes = b.bytes[8:]

	case reflect.Slice:
		// If we encounter a slice, we consume the rest of the data
		// in the buffer and load it into the slice.
		for len(b.bytes) > 0 {
			newElementPtr := reflect.New(value.Type().Elem())
			if err := b.read(newElementPtr.Interface()); err != nil {
				return err
			}
			if value.CanSet() {
				value.Set(reflect.Append(value, reflect.Indirect(newElementPtr)))
			}
		}

	case reflect.Struct:
		for i := 0; i < value.NumField(); i++ {
			encoding := value.Type().Field(i).Tag.Get("encoding")
			if encoding == omit {
				continue
			}
			if value.Field(i).CanInterface() {
				field := value.Field(i)
				newFieldPtr := reflect.NewAt(
					//nolint:gosec // This is the only way to get a typed pointer to a structure's field
					field.Type(), unsafe.Pointer(field.UnsafeAddr()),
				)
				if err := b.read(newFieldPtr.Interface()); err != nil {
					return err
				}
			} else {
				advance := int(value.Field(i).Type().Size()) //nolint:gosec //Size comes from type system and is bounded
				if len(b.bytes) < advance {
					return errWrongMarshalSize
				}
				b.bytes = b.bytes[advance:]
			}
		}

	default:
		return errBadStructMemberType
	}

	return nil
}

// Consumes `size` bytes and returns them as an
// independent PacketBuffer.
func (b *packetBuffer) split(size int) packetBuffer {
	if size > len(b.bytes) {
		size = len(b.bytes)
	}
	newBuffer := packetBuffer{bytes: b.bytes[:size]}

	b.bytes = b.bytes[size:]

	return newBuffer
}

// Returns the size that a structure will encode into.
// This fuction doesn't check that Write() will succeed,
// and may return unexpectedly large results for those
// structures that Write() will fail on.
func wireSize(v any) int {
	value := reflect.ValueOf(v)
	// Indirect is safe to call on non-pointers, and
	// will simply return the same value in such cases
	value = reflect.Indirect(value)
	size := int(0)

	switch value.Kind() {
	case reflect.Slice:
		for i := 0; i < value.Len(); i++ {
			if value.Index(i).CanInterface() {
				size += wireSize(value.Index(i).Interface())
			} else {
				size += int(value.Index(i).Type().Size()) //nolint:gosec //  RTCP element sizes are small and bounded
			}
		}

	case reflect.Struct:
		for i := 0; i < value.NumField(); i++ {
			encoding := value.Type().Field(i).Tag.Get("encoding")
			if encoding == omit {
				continue
			}
			if value.Field(i).CanInterface() {
				size += wireSize(value.Field(i).Interface())
			} else {
				size += int(value.Field(i).Type().Size()) // nolint:gosec // Size comes from type system and is bounded
			}
		}

	default:
		size = int(value.Type().Size()) // nolint:gosec // Size comes from type system and is bounde
	}

	return size
}


================================================
FILE: packet_buffer_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestWrite(t *testing.T) {
	type Subtree struct {
		SubA uint32
		SubB []uint8
	}

	structure := struct {
		A uint8
		Z uint32 `encoding:"omit"`
		B uint16
		C uint32
		D uint64
		_ uint8
		E []uint16
		F Subtree
		G []Subtree
	}{
		0xf8,
		0x01234567,
		0x1234,
		0x56789ABC,
		0x0102030405060708,
		0x12,
		[]uint16{0x0E, 0x02FF},
		Subtree{0x11223344, []uint8{9, 8, 7, 6, 5, 4, 3, 2, 1}},
		[]Subtree{{0x01, []uint8{1, 2, 3, 4}}, {0x02, []uint8{5, 6, 7, 8}}},
	}
	expected := []byte{
		0xf8,
		0x12, 0x34,
		0x56, 0x78, 0x9A, 0xBC,
		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
		0x00,
		0x00, 0x0E, 0x02, 0xFF,
		0x11, 0x22, 0x33, 0x44, 9, 8, 7, 6, 5, 4, 3, 2, 1,
		0x00, 0x00, 0x00, 0x01, 1, 2, 3, 4, 0x00, 0x00, 0x00, 0x02, 5, 6, 7, 8,
	}
	assert.Equal(t, len(expected), wireSize(structure))

	raw := make([]byte, len(expected))
	buffer := packetBuffer{bytes: raw}
	err := buffer.write(structure)
	assert.NoError(t, err)
	assert.Equal(t, expected, raw)

	// Check for overflow
	raw = make([]byte, len(expected)-1)
	buffer = packetBuffer{bytes: raw}
	err = buffer.write(structure)
	assert.ErrorIs(t, err, errWrongMarshalSize)
}

func TestReadUint8(t *testing.T) {
	const expected = 0x01
	raw := []byte{expected}
	output := uint8(0)
	buffer := packetBuffer{bytes: raw}
	err := buffer.read(&output)
	assert.NoError(t, err)
	assert.Equal(t, uint8(expected), output)
}

func TestReadUint16(t *testing.T) {
	const expected = 0x0102
	raw := []byte{0x01, 0x02}
	output := uint16(0)
	buffer := packetBuffer{bytes: raw}
	err := buffer.read(&output)
	assert.NoError(t, err)
	assert.Equal(t, uint16(expected), output)
}

func TestReadUint32(t *testing.T) {
	const expected = 0x01020304
	raw := []byte{0x01, 0x02, 0x03, 0x04}
	output := uint32(0)
	buffer := packetBuffer{bytes: raw}
	err := buffer.read(&output)
	assert.NoError(t, err)
	assert.Equal(t, uint32(expected), output)
}

func TestReadUint64(t *testing.T) {
	expected := uint64(0x0102030405060708)
	raw := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
	output := uint64(0)
	buffer := packetBuffer{bytes: raw}
	err := buffer.read(&output)
	assert.NoError(t, err)
	assert.Equal(t, expected, output)
}

func TestReadStruct(t *testing.T) {
	type S struct {
		A uint8
		B uint16
		C uint32
		D uint64
	}
	expected := S{0x01, 0x0203, 0x04050607, 0x08090A0B0C0D0E0F}
	raw := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}
	var output S
	buffer := packetBuffer{bytes: raw}
	err := buffer.read(&output)
	assert.NoError(t, err)
	assert.Equal(t, expected, output)
}

func TestReadSlice(t *testing.T) {
	expected := []uint16{0x0102, 0x0304, 0x0506, 0x0708}
	raw := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
	var output []uint16
	buffer := packetBuffer{bytes: raw}
	err := buffer.read(&output)
	assert.NoError(t, err)
	assert.Equal(t, expected, output)
}

func TestReadComplex(t *testing.T) {
	raw := []byte{
		0xf8,
		0x12, 0x34,
		0x56, 0x78, 0x9A, 0xBC,
		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
		0x12,
		0x11, 0x22, 0x33, 0x44, 9,
		0x00, 0x00, 0x00, 0x01, 1, 0x00, 0x00, 0x00, 0x02, 5,
	}

	type Subtree struct {
		SubA uint32
		SubB uint8
	}

	type Tree struct {
		A uint8
		B uint16
		C uint32
		D uint64
		_ uint8
		F Subtree
		G []Subtree
	}

	expected := Tree{
		0xf8,
		0x1234,
		0x56789ABC,
		0x0102030405060708,
		0x00,
		Subtree{0x11223344, 9},
		[]Subtree{{0x01, 1}, {0x02, 5}},
	}

	var output Tree

	buffer := packetBuffer{bytes: raw}
	err := buffer.read(&output)
	assert.NoError(t, err)
	assert.Equal(t, expected, output)
}


================================================
FILE: packet_stringifier.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"fmt"
	"reflect"
)

/*
Converts an RTCP Packet into a human-readable format. The Packets
themselves can control the presentation as follows:

  - Fields of a type that have a String() method will be formatted
    with that String method (which should not emit '\n' characters)

  - Otherwise, fields with a tag containing a "fmt" string will use that
    format when serializing the value. For example, to format an SSRC
    value as base 16 insted of base 10:

    type ExamplePacket struct {
    LocalSSRC   uint32   `fmt:"0x%X"`
    RemotsSSRCs []uint32 `fmt:"%X"`
    }

- If no fmt string is present, "%+v" is used by default

The intention of this stringify() function is to simplify creation
of String() methods on new packet types, as it provides a simple
baseline implementation that works well in the majority of cases.
*/
func stringify(p Packet) string {
	value := reflect.Indirect(reflect.ValueOf(p))

	return formatField(value.Type().String(), "", p, "")
}

//nolint:gocognit,cyclop
func formatField(name string, format string, f any, indent string) string {
	out := indent
	value := reflect.ValueOf(f)

	if !value.IsValid() {
		return fmt.Sprintf("%s%s: <nil>\n", out, name)
	}

	isPacket := reflect.TypeOf(f).Implements(reflect.TypeFor[Packet]())

	// Resolve pointers to their underlying values
	if value.Type().Kind() == reflect.Ptr && !value.IsNil() {
		underlying := reflect.Indirect(value)
		if underlying.IsValid() {
			value = underlying
		}
	}

	// If the field type has a custom String method, use that
	// (unless we're a packet, since we want to avoid recursing
	// back into this function if the Packet's String() method
	// uses it)
	if stringMethod := value.MethodByName("String"); !isPacket && stringMethod.IsValid() {
		out += fmt.Sprintf("%s: %s\n", name, stringMethod.Call([]reflect.Value{}))

		return out
	}

	switch value.Kind() {
	case reflect.Struct:
		out += fmt.Sprintf("%s:\n", name)
		for i := 0; i < value.NumField(); i++ {
			if value.Field(i).CanInterface() {
				format = value.Type().Field(i).Tag.Get("fmt")
				if format == "" {
					format = "%+v"
				}
				out += formatField(value.Type().Field(i).Name, format, value.Field(i).Interface(), indent+"\t")
			}
		}
	case reflect.Slice:
		childKind := value.Type().Elem().Kind()
		_, hasStringMethod := value.Type().Elem().MethodByName("String")
		if hasStringMethod || childKind == reflect.Struct || childKind == reflect.Ptr ||
			childKind == reflect.Interface || childKind == reflect.Slice {
			out += fmt.Sprintf("%s:\n", name)
			for i := 0; i < value.Len(); i++ {
				childName := fmt.Sprint(i)
				// Since interfaces can hold different types of things, we add the
				// most specific type name to the name to make it clear what the
				// subsequent fields represent.
				if value.Index(i).Kind() == reflect.Interface {
					childName += fmt.Sprintf(" (%s)", reflect.Indirect(reflect.ValueOf(value.Index(i).Interface())).Type())
				}
				if value.Index(i).CanInterface() {
					out += formatField(childName, format, value.Index(i).Interface(), indent+"\t")
				}
			}

			return out
		}

		// If we didn't take care of stringing the value already, we fall through to the
		// generic case. This will print slices of basic types on a single line.
		fallthrough
	default:
		if value.CanInterface() {
			out += fmt.Sprintf("%s: "+format+"\n", name, value.Interface())
		}
	}

	return out
}


================================================
FILE: packet_stringifier_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

//nolint:maintidx
func TestPrint(t *testing.T) {
	type Tests struct {
		packet   Packet
		expected string
	}

	tests := []Tests{
		{
			&ExtendedReport{
				SenderSSRC: 0x01020304,
				Reports: []ReportBlock{
					&LossRLEReportBlock{
						XRHeader: XRHeader{
							BlockType: LossRLEReportBlockType,
						},
						SSRC:     0x12345689,
						BeginSeq: 5,
						EndSeq:   12,
						Chunks: []Chunk{
							Chunk(0x4006),
							Chunk(0x0006),
							Chunk(0x8765),
							Chunk(0x0000),
						},
					},
					&DuplicateRLEReportBlock{
						XRHeader: XRHeader{
							BlockType: DuplicateRLEReportBlockType,
						},
						SSRC:     0x12345689,
						BeginSeq: 5,
						EndSeq:   12,
						Chunks: []Chunk{
							Chunk(0x4123),
							Chunk(0x3FFF),
							Chunk(0xFFFF),
							Chunk(0x0000),
						},
					},
					&PacketReceiptTimesReportBlock{
						XRHeader: XRHeader{
							BlockType: PacketReceiptTimesReportBlockType,
						},
						SSRC:     0x98765432,
						BeginSeq: 15432,
						EndSeq:   15577,
						ReceiptTime: []uint32{
							0x11111111,
							0x22222222,
							0x33333333,
							0x44444444,
							0x55555555,
						},
					},
					&ReceiverReferenceTimeReportBlock{
						XRHeader: XRHeader{
							BlockType: ReceiverReferenceTimeReportBlockType,
						},
						NTPTimestamp: 0x0102030405060708,
					},
					&DLRRReportBlock{
						XRHeader: XRHeader{
							BlockType: DLRRReportBlockType,
						},
						Reports: []DLRRReport{
							{
								SSRC:   0x88888888,
								LastRR: 0x12345678,
								DLRR:   0x99999999,
							},
							{
								SSRC:   0x09090909,
								LastRR: 0x12345678,
								DLRR:   0x99999999,
							},
							{
								SSRC:   0x11223344,
								LastRR: 0x12345678,
								DLRR:   0x99999999,
							},
						},
					},
					&StatisticsSummaryReportBlock{
						XRHeader{
							BlockType: StatisticsSummaryReportBlockType,
						},
						true, true, true, ToHIPv4,
						0xFEDCBA98,
						0x1234, 0x5678,
						0x11111111,
						0x22222222,
						0x33333333,
						0x44444444,
						0x55555555,
						0x66666666,
						0x01, 0x02, 0x03, 0x04,
					},
					&VoIPMetricsReportBlock{
						XRHeader{
							BlockType: VoIPMetricsReportBlockType,
						},
						0x89ABCDEF,
						0x05, 0x06, 0x07, 0x08,
						0x1111, 0x2222, 0x3333, 0x4444,
						0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
						0x00,
						0x1122, 0x3344, 0x5566,
					},
				},
			},
			// nolint
			"rtcp.ExtendedReport:\n" +
				"\tSenderSSRC: 0x1020304\n" +
				"\tReports:\n" +
				"\t\t0 (rtcp.LossRLEReportBlock):\n" +
				"\t\t\tXRHeader:\n" +
				"\t\t\t\tBlockType: [LossRLEReportBlockType]\n" +
				"\t\t\t\tTypeSpecific: 0x0\n" +
				"\t\t\t\tBlockLength: 0\n" +
				"\t\t\tT: 0\n" +
				"\t\t\tSSRC: 0x12345689\n" +
				"\t\t\tBeginSeq: 5\n" +
				"\t\t\tEndSeq: 12\n" +
				"\t\t\tChunks:\n" +
				"\t\t\t\t0: [[RunLength type=1, length=6]]\n" +
				"\t\t\t\t1: [[RunLength type=0, length=6]]\n" +
				"\t\t\t\t2: [[BitVector 0b000011101100101]]\n" +
				"\t\t\t\t3: [[TerminatingNull]]\n" +
				"\t\t1 (rtcp.DuplicateRLEReportBlock):\n" +
				"\t\t\tXRHeader:\n" +
				"\t\t\t\tBlockType: [DuplicateRLEReportBlockType]\n" +
				"\t\t\t\tTypeSpecific: 0x0\n" +
				"\t\t\t\tBlockLength: 0\n" +
				"\t\t\tT: 0\n" +
				"\t\t\tSSRC: 0x12345689\n" +
				"\t\t\tBeginSeq: 5\n" +
				"\t\t\tEndSeq: 12\n" +
				"\t\t\tChunks:\n" +
				"\t\t\t\t0: [[RunLength type=1, length=291]]\n" +
				"\t\t\t\t1: [[RunLength type=0, length=16383]]\n" +
				"\t\t\t\t2: [[BitVector 0b111111111111111]]\n" +
				"\t\t\t\t3: [[TerminatingNull]]\n" +
				"\t\t2 (rtcp.PacketReceiptTimesReportBlock):\n" +
				"\t\t\tXRHeader:\n" +
				"\t\t\t\tBlockType: [PacketReceiptTimesReportBlockType]\n" +
				"\t\t\t\tTypeSpecific: 0x0\n" +
				"\t\t\t\tBlockLength: 0\n" +
				"\t\t\tT: 0\n" +
				"\t\t\tSSRC: 0x98765432\n" +
				"\t\t\tBeginSeq: 15432\n" +
				"\t\t\tEndSeq: 15577\n" +
				"\t\t\tReceiptTime: [286331153 572662306 858993459 1145324612 1431655765]\n" +
				"\t\t3 (rtcp.ReceiverReferenceTimeReportBlock):\n" +
				"\t\t\tXRHeader:\n" +
				"\t\t\t\tBlockType: [ReceiverReferenceTimeReportBlockType]\n" +
				"\t\t\t\tTypeSpecific: 0x0\n" +
				"\t\t\t\tBlockLength: 0\n" +
				"\t\t\tNTPTimestamp: 72623859790382856\n" +
				"\t\t4 (rtcp.DLRRReportBlock):\n" +
				"\t\t\tXRHeader:\n" +
				"\t\t\t\tBlockType: [DLRRReportBlockType]\n" +
				"\t\t\t\tTypeSpecific: 0x0\n" +
				"\t\t\t\tBlockLength: 0\n" +
				"\t\t\tReports:\n" +
				"\t\t\t\t0:\n" +
				"\t\t\t\t\tSSRC: 0x88888888\n" +
				"\t\t\t\t\tLastRR: 305419896\n" +
				"\t\t\t\t\tDLRR: 2576980377\n" +
				"\t\t\t\t1:\n" +
				"\t\t\t\t\tSSRC: 0x9090909\n" +
				"\t\t\t\t\tLastRR: 305419896\n" +
				"\t\t\t\t\tDLRR: 2576980377\n" +
				"\t\t\t\t2:\n" +
				"\t\t\t\t\tSSRC: 0x11223344\n" +
				"\t\t\t\t\tLastRR: 305419896\n" +
				"\t\t\t\t\tDLRR: 2576980377\n" +
				"\t\t5 (rtcp.StatisticsSummaryReportBlock):\n" +
				"\t\t\tXRHeader:\n" +
				"\t\t\t\tBlockType: [StatisticsSummaryReportBlockType]\n" +
				"\t\t\t\tTypeSpecific: 0x0\n" +
				"\t\t\t\tBlockLength: 0\n" +
				"\t\t\tLossReports: true\n" +
				"\t\t\tDuplicateReports: true\n" +
				"\t\t\tJitterReports: true\n" +
				"\t\t\tTTLorHopLimit: [[ToH = IPv4]]\n" +
				"\t\t\tSSRC: 0xFEDCBA98\n" +
				"\t\t\tBeginSeq: 4660\n" +
				"\t\t\tEndSeq: 22136\n" +
				"\t\t\tLostPackets: 286331153\n" +
				"\t\t\tDupPackets: 572662306\n" +
				"\t\t\tMinJitter: 858993459\n" +
				"\t\t\tMaxJitter: 1145324612\n" +
				"\t\t\tMeanJitter: 1431655765\n" +
				"\t\t\tDevJitter: 1717986918\n" +
				"\t\t\tMinTTLOrHL: 1\n" +
				"\t\t\tMaxTTLOrHL: 2\n" +
				"\t\t\tMeanTTLOrHL: 3\n" +
				"\t\t\tDevTTLOrHL: 4\n" +
				"\t\t6 (rtcp.VoIPMetricsReportBlock):\n" +
				"\t\t\tXRHeader:\n" +
				"\t\t\t\tBlockType: [VoIPMetricsReportBlockType]\n" +
				"\t\t\t\tTypeSpecific: 0x0\n" +
				"\t\t\t\tBlockLength: 0\n" +
				"\t\t\tSSRC: 0x89ABCDEF\n" +
				"\t\t\tLossRate: 5\n" +
				"\t\t\tDiscardRate: 6\n" +
				"\t\t\tBurstDensity: 7\n" +
				"\t\t\tGapDensity: 8\n" +
				"\t\t\tBurstDuration: 4369\n" +
				"\t\t\tGapDuration: 8738\n" +
				"\t\t\tRoundTripDelay: 13107\n" +
				"\t\t\tEndSystemDelay: 17476\n" +
				"\t\t\tSignalLevel: 17\n" +
				"\t\t\tNoiseLevel: 34\n" +
				"\t\t\tRERL: 51\n" +
				"\t\t\tGmin: 68\n" +
				"\t\t\tRFactor: 85\n" +
				"\t\t\tExtRFactor: 102\n" +
				"\t\t\tMOSLQ: 119\n" +
				"\t\t\tMOSCQ: 136\n" +
				"\t\t\tRXConfig: 153\n" +
				"\t\t\tJBNominal: 4386\n" +
				"\t\t\tJBMaximum: 13124\n" +
				"\t\t\tJBAbsMax: 21862\n",
		},
		{
			&FullIntraRequest{
				SenderSSRC: 0x0,
				MediaSSRC:  0x4bc4fcb4,
				FIR: []FIREntry{
					{
						SSRC:           0x12345678,
						SequenceNumber: 0x42,
					},
					{
						SSRC:           0x98765432,
						SequenceNumber: 0x57,
					},
				},
			},
			// nolint
			"rtcp.FullIntraRequest:\n" +
				"\tSenderSSRC: 0\n" +
				"\tMediaSSRC: 1271200948\n" +
				"\tFIR:\n" +
				"\t\t0:\n" +
				"\t\t\tSSRC: 305419896\n" +
				"\t\t\tSequenceNumber: 66\n" +
				"\t\t1:\n" +
				"\t\t\tSSRC: 2557891634\n" +
				"\t\t\tSequenceNumber: 87\n",
		},
		{
			&Goodbye{
				Sources: []uint32{
					0x01020304,
					0x05060708,
				},
				Reason: "because",
			},
			"rtcp.Goodbye:\n" +
				"\tSources: [16909060 84281096]\n" +
				"\tReason: because\n",
		},
		{
			&ReceiverReport{
				SSRC: 0x902f9e2e,
				Reports: []ReceptionReport{{
					SSRC:               0xbc5e9a40,
					FractionLost:       0,
					TotalLost:          0,
					LastSequenceNumber: 0x46e1,
					Jitter:             273,
					LastSenderReport:   0x9f36432,
					Delay:              150137,
				}},
				ProfileExtensions: []byte{},
			},
			"rtcp.ReceiverReport:\n" +
				"\tSSRC: 2419039790\n" +
				"\tReports:\n" +
				"\t\t0:\n" +
				"\t\t\tSSRC: 3160316480\n" +
				"\t\t\tFractionLost: 0\n" +
				"\t\t\tTotalLost: 0\n" +
				"\t\t\tLastSequenceNumber: 18145\n" +
				"\t\t\tJitter: 273\n" +
				"\t\t\tLastSenderReport: 166945842\n" +
				"\t\t\tDelay: 150137\n" +
				"\tProfileExtensions: []\n",
		},
		{
			NewCNAMESourceDescription(0x902f9e2e, "{9c00eb92-1afb-9d49-a47d-91f64eee69f5}"),
			"rtcp.SourceDescription:\n" +
				"\tChunks:\n" +
				"\t\t0:\n" +
				"\t\t\tSource: 2419039790\n" +
				"\t\t\tItems:\n" +
				"\t\t\t\t0:\n" +
				"\t\t\t\t\tType: [CNAME]\n" +
				"\t\t\t\t\tText: {9c00eb92-1afb-9d49-a47d-91f64eee69f5}\n",
		},
		{
			&PictureLossIndication{
				SenderSSRC: 0x902f9e2e,
				MediaSSRC:  0x902f9e2e,
			},
			// nolint
			"rtcp.PictureLossIndication:\n" +
				"\tSenderSSRC: 2419039790\n" +
				"\tMediaSSRC: 2419039790\n",
		},
		{
			&RapidResynchronizationRequest{
				SenderSSRC: 0x902f9e2e,
				MediaSSRC:  0x902f9e2e,
			},
			"rtcp.RapidResynchronizationRequest:\n" +
				"\tSenderSSRC: 2419039790\n" +
				"\tMediaSSRC: 2419039790\n",
		},
		{
			&ReceiverEstimatedMaximumBitrate{
				SenderSSRC: 1,
				Bitrate:    8927168,
				SSRCs:      []uint32{1215622422},
			},
			"rtcp.ReceiverEstimatedMaximumBitrate:\n" +
				"\tSenderSSRC: 1\n" +
				"\tBitrate: 8.927168e+06\n" +
				"\tSSRCs: [1215622422]\n",
		},
		{
			&SenderReport{
				SSRC:        0x902f9e2e,
				NTPTime:     0xda8bd1fcdddda05a,
				RTPTime:     0xaaf4edd5,
				PacketCount: 1,
				OctetCount:  2,
				Reports: []ReceptionReport{{
					SSRC:               0xbc5e9a40,
					FractionLost:       0,
					TotalLost:          0,
					LastSequenceNumber: 0x46e1,
					Jitter:             273,
					LastSenderReport:   0x9f36432,
					Delay:              150137,
				}},
				ProfileExtensions: []byte{
					0x81, 0xca, 0x0, 0x6,
					0x2b, 0x7e, 0xc0, 0xc5,
					0x1, 0x10, 0x4c, 0x63,
					0x49, 0x66, 0x7a, 0x58,
					0x6f, 0x6e, 0x44, 0x6f,
					0x72, 0x64, 0x53, 0x65,
					0x57, 0x36, 0x0, 0x0,
				},
			},
			"rtcp.SenderReport:\n" +
				"\tSSRC: 2419039790\n" +
				"\tNTPTime: 15747911406015324250\n" +
				"\tRTPTime: 2868178389\n" +
				"\tPacketCount: 1\n" +
				"\tOctetCount: 2\n" +
				"\tReports:\n" +
				"\t\t0:\n" +
				"\t\t\tSSRC: 3160316480\n" +
				"\t\t\tFractionLost: 0\n" +
				"\t\t\tTotalLost: 0\n" +
				"\t\t\tLastSequenceNumber: 18145\n" +
				"\t\t\tJitter: 273\n" +
				"\t\t\tLastSenderReport: 166945842\n" +
				"\t\t\tDelay: 150137\n" +
				"\tProfileExtensions: " +
				"[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",
		},
		{
			&SliceLossIndication{
				SenderSSRC: 0x902f9e2e,
				MediaSSRC:  0x902f9e2e,
				SLI:        []SLIEntry{{0xaaa, 0, 0x2C}},
			},
			"rtcp.SliceLossIndication:\n" +
				"\tSenderSSRC: 2419039790\n" +
				"\tMediaSSRC: 2419039790\n" +
				"\tSLI:\n" +
				"\t\t0:\n" +
				"\t\t\tFirst: 2730\n" +
				"\t\t\tNumber: 0\n" +
				"\t\t\tPicture: 44\n",
		},
		{
			&SourceDescription{
				Chunks: []SourceDescriptionChunk{
					{
						Source: 0x10000000,
						Items: []SourceDescriptionItem{
							{
								Type: SDESCNAME,
								Text: "A",
							},
							{
								Type: SDESPhone,
								Text: "B",
							},
						},
					},
				},
			},
			"rtcp.SourceDescription:\n" +
				"\tChunks:\n" +
				"\t\t0:\n" +
				"\t\t\tSource: 268435456\n" +
				"\t\t\tItems:\n" +
				"\t\t\t\t0:\n" +
				"\t\t\t\t\tType: [CNAME]\n" +
				"\t\t\t\t\tText: A\n" +
				"\t\t\t\t1:\n" +
				"\t\t\t\t\tType: [PHONE]\n" +
				"\t\t\t\t\tText: B\n",
		},
		{
			&TransportLayerCC{
				Header: Header{
					Padding: true,
					Count:   FormatTCC,
					Type:    TypeTransportSpecificFeedback,
					Length:  5,
				},
				SenderSSRC:         4195875351,
				MediaSSRC:          1124282272,
				BaseSequenceNumber: 153,
				PacketStatusCount:  1,
				ReferenceTime:      4057090,
				FbPktCount:         23,
				// 0b00100000, 0b00000001
				PacketChunks: []PacketStatusChunk{
					&RunLengthChunk{
						Type:               TypeTCCRunLengthChunk,
						PacketStatusSymbol: TypeTCCPacketReceivedSmallDelta,
						RunLength:          1,
					},
				},
				// 0b10010100
				RecvDeltas: []*RecvDelta{
					{
						Type:  TypeTCCPacketReceivedSmallDelta,
						Delta: 37000,
					},
				},
			},
			"rtcp.TransportLayerCC:\n" +
				"\tHeader:\n" +
				"\t\tPadding: true\n" +
				"\t\tCount: 15\n" +
				"\t\tType: [TSFB]\n" +
				"\t\tLength: 5\n" +
				"\tSenderSSRC: 4195875351\n" +
				"\tMediaSSRC: 1124282272\n" +
				"\tBaseSequenceNumber: 153\n" +
				"\tPacketStatusCount: 1\n" +
				"\tReferenceTime: 4057090\n" +
				"\tFbPktCount: 23\n" +
				"\tPacketChunks:\n" +
				"\t\t0 (rtcp.RunLengthChunk):\n" +
				"\t\t\tPacketStatusChunk: <nil>\n" +
				"\t\t\tType: 0\n" +
				"\t\t\tPacketStatusSymbol: 1\n" +
				"\t\t\tRunLength: 1\n" +
				"\tRecvDeltas:\n" +
				"\t\t0:\n" +
				"\t\t\tType: 1\n" +
				"\t\t\tDelta: 37000\n",
		},
		{
			&TransportLayerNack{
				SenderSSRC: 0x902f9e2e,
				MediaSSRC:  0x902f9e2e,
				Nacks:      []NackPair{{1, 0xAA}, {1034, 0x05}},
			},
			"rtcp.TransportLayerNack:\n" +
				"\tSenderSSRC: 2419039790\n" +
				"\tMediaSSRC: 2419039790\n" +
				"\tNacks:\n" +
				"\t\t0:\n" +
				"\t\t\tPacketID: 1\n" +
				"\t\t\tLostPackets: 170\n" +
				"\t\t1:\n" +
				"\t\t\tPacketID: 1034\n" +
				"\t\t\tLostPackets: 5\n",
		},
	}

	for i, test := range tests {
		assert.Equalf(t, test.expected, stringify(test.packet), "Error stringifying test %d", i)
	}
}


================================================
FILE: packet_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

// An RTCP packet from a packet dump.
func realPacket() []byte {
	return []byte{
		// Receiver Report (offset=0)
		// v=2, p=0, count=1, RR, len=7
		0x81, 0xc9, 0x0, 0x7,
		// ssrc=0x902f9e2e
		0x90, 0x2f, 0x9e, 0x2e,
		// ssrc=0xbc5e9a40
		0xbc, 0x5e, 0x9a, 0x40,
		// fracLost=0, totalLost=0
		0x0, 0x0, 0x0, 0x0,
		// lastSeq=0x46e1
		0x0, 0x0, 0x46, 0xe1,
		// jitter=273
		0x0, 0x0, 0x1, 0x11,
		// lsr=0x9f36432
		0x9, 0xf3, 0x64, 0x32,
		// delay=150137
		0x0, 0x2, 0x4a, 0x79,

		// Source Description (offset=32)
		// v=2, p=0, count=1, SDES, len=12
		0x81, 0xca, 0x0, 0xc,
		// ssrc=0x902f9e2e
		0x90, 0x2f, 0x9e, 0x2e,
		// CNAME, len=38
		0x1, 0x26,
		// text="{9c00eb92-1afb-9d49-a47d-91f64eee69f5}"
		0x7b, 0x39, 0x63, 0x30,
		0x30, 0x65, 0x62, 0x39,
		0x32, 0x2d, 0x31, 0x61,
		0x66, 0x62, 0x2d, 0x39,
		0x64, 0x34, 0x39, 0x2d,
		0x61, 0x34, 0x37, 0x64,
		0x2d, 0x39, 0x31, 0x66,
		0x36, 0x34, 0x65, 0x65,
		0x65, 0x36, 0x39, 0x66,
		0x35, 0x7d,
		// END + padding
		0x0, 0x0, 0x0, 0x0,

		// Goodbye (offset=84)
		// v=2, p=0, count=1, BYE, len=1
		0x81, 0xcb, 0x0, 0x1,
		// source=0x902f9e2e
		0x90, 0x2f, 0x9e, 0x2e,

		// Picture Loss Indication (offset=92)
		0x81, 0xce, 0x0, 0x2,
		// sender=0x902f9e2e
		0x90, 0x2f, 0x9e, 0x2e,
		// media=0x902f9e2e
		0x90, 0x2f, 0x9e, 0x2e,

		// RapidResynchronizationRequest (offset=104)
		0x85, 0xcd, 0x0, 0x2,
		// sender=0x902f9e2e
		0x90, 0x2f, 0x9e, 0x2e,
		// media=0x902f9e2e
		0x90, 0x2f, 0x9e, 0x2e,

		// ApplicationDefined (offset=116)
		0x80, 0xcc, 0x00, 0x03,
		// sender=0x4baae1ab
		0x4b, 0xaa, 0xe1, 0xab,
		// name='NAME'
		0x4E, 0x41, 0x4D, 0x45,
		// data='ABCD'
		0x41, 0x42, 0x43, 0x44,
	}
}

func TestUnmarshal(t *testing.T) {
	packet, err := Unmarshal(realPacket())
	assert.NoError(t, err)

	expected := []Packet{
		&ReceiverReport{
			SSRC: 0x902f9e2e,
			Reports: []ReceptionReport{{
				SSRC:               0xbc5e9a40,
				FractionLost:       0,
				TotalLost:          0,
				LastSequenceNumber: 0x46e1,
				Jitter:             273,
				LastSenderReport:   0x9f36432,
				Delay:              150137,
			}},
			ProfileExtensions: []byte{},
		},
		NewCNAMESourceDescription(0x902f9e2e, "{9c00eb92-1afb-9d49-a47d-91f64eee69f5}"),
		&Goodbye{
			Sources: []uint32{0x902f9e2e},
		},
		&PictureLossIndication{
			SenderSSRC: 0x902f9e2e,
			MediaSSRC:  0x902f9e2e,
		},
		&RapidResynchronizationRequest{
			SenderSSRC: 0x902f9e2e,
			MediaSSRC:  0x902f9e2e,
		},
		&ApplicationDefined{
			SSRC: 0x4baae1ab,
			Name: "NAME",
			Data: []byte{0x41, 0x42, 0x43, 0x44},
		},
	}

	assert.Equal(t, expected, packet)
}

func TestUnmarshalNil(t *testing.T) {
	_, err := Unmarshal(nil)
	assert.ErrorIs(t, err, errInvalidHeader)
}

func TestInvalidHeaderLength(t *testing.T) {
	invalidPacket := []byte{
		// Receiver Report (offset=0)
		// v=2, p=0, count=1, RR, len=100
		0x81, 0xc9, 0x0, 0x64,
	}

	_, err := Unmarshal(invalidPacket)
	assert.ErrorIs(t, err, errPacketTooShort)
}


================================================
FILE: picture_loss_indication.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"encoding/binary"
	"fmt"
)

// The PictureLossIndication packet informs the encoder about the loss of an undefined amount of
// coded video data belonging to one or more pictures.
type PictureLossIndication struct {
	// SSRC of sender
	SenderSSRC uint32

	// SSRC where the loss was experienced
	MediaSSRC uint32
}

const (
	pliLength = 2
)

// Marshal encodes the PictureLossIndication in binary.
func (p PictureLossIndication) Marshal() ([]byte, error) {
	/*
	 * PLI does not require parameters.  Therefore, the length field MUST be
	 * 2, and there MUST NOT be any Feedback Control Information.
	 *
	 * The semantics of this FB message is independent of the payload type.
	 */
	rawPacket := make([]byte, p.MarshalSize())
	packetBody := rawPacket[headerLength:]

	binary.BigEndian.PutUint32(packetBody, p.SenderSSRC)
	binary.BigEndian.PutUint32(packetBody[4:], p.MediaSSRC)

	h := Header{
		Count:  FormatPLI,
		Type:   TypePayloadSpecificFeedback,
		Length: pliLength,
	}
	hData, err := h.Marshal()
	if err != nil {
		return nil, err
	}
	copy(rawPacket, hData)

	return rawPacket, nil
}

// Unmarshal decodes the PictureLossIndication from binary.
func (p *PictureLossIndication) Unmarshal(rawPacket []byte) error {
	if len(rawPacket) < (headerLength + (ssrcLength * 2)) {
		return errPacketTooShort
	}

	var h Header
	if err := h.Unmarshal(rawPacket); err != nil {
		return err
	}

	if h.Type != TypePayloadSpecificFeedback || h.Count != FormatPLI {
		return errWrongType
	}

	p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
	p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])

	return nil
}

// Header returns the Header associated with this packet.
func (p *PictureLossIndication) Header() Header {
	return Header{
		Count:  FormatPLI,
		Type:   TypePayloadSpecificFeedback,
		Length: pliLength,
	}
}

// MarshalSize returns the size of the packet once marshaled.
func (p *PictureLossIndication) MarshalSize() int {
	return headerLength + ssrcLength*2
}

func (p *PictureLossIndication) String() string {
	return fmt.Sprintf("PictureLossIndication %x %x", p.SenderSSRC, p.MediaSSRC)
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *PictureLossIndication) DestinationSSRC() []uint32 {
	return []uint32{p.MediaSSRC}
}


================================================
FILE: picture_loss_indication_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

var _ Packet = (*PictureLossIndication)(nil) // assert is a Packet

func TestPictureLossIndicationUnmarshal(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Data      []byte
		Want      PictureLossIndication
		WantError error
	}{
		{
			Name: "valid",
			Data: []byte{
				// v=2, p=0, FMT=1, PSFB, len=1
				0x81, 0xce, 0x00, 0x02,
				// ssrc=0x0
				0x00, 0x00, 0x00, 0x00,
				// ssrc=0x4bc4fcb4
				0x4b, 0xc4, 0xfc, 0xb4,
			},
			Want: PictureLossIndication{
				SenderSSRC: 0x0,
				MediaSSRC:  0x4bc4fcb4,
			},
		},
		{
			Name: "packet too short",
			Data: []byte{
				0x00, 0x00, 0x00, 0x00,
			},
			WantError: errPacketTooShort,
		},
		{
			Name: "invalid header",
			Data: []byte{
				0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00,
			},
			WantError: errBadVersion,
		},
		{
			Name: "wrong type",
			Data: []byte{
				// v=2, p=0, FMT=1, RR, len=1
				0x81, 0xc9, 0x00, 0x02,
				// ssrc=0x0
				0x00, 0x00, 0x00, 0x00,
				// ssrc=0x4bc4fcb4
				0x4b, 0xc4, 0xfc, 0xb4,
			},
			WantError: errWrongType,
		},
		{
			Name: "wrong fmt",
			Data: []byte{
				// v=2, p=0, FMT=2, RR, len=1
				0x82, 0xc9, 0x00, 0x02,
				// ssrc=0x0
				0x00, 0x00, 0x00, 0x00,
				// ssrc=0x4bc4fcb4
				0x4b, 0xc4, 0xfc, 0xb4,
			},
			WantError: errWrongType,
		},
	} {
		var pli PictureLossIndication
		err := pli.Unmarshal(test.Data)
		assert.ErrorIsf(t, err, test.WantError, "Unmarshal %q", test.Name)
		if err != nil {
			continue
		}

		assert.Equalf(t, test.Want, pli, "Unmarshal %q", test.Name)
	}
}

func TestPictureLossIndicationRoundTrip(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Packet    PictureLossIndication
		WantError error
	}{
		{
			Name: "valid",
			Packet: PictureLossIndication{
				SenderSSRC: 1,
				MediaSSRC:  2,
			},
		},
		{
			Name: "also valid",
			Packet: PictureLossIndication{
				SenderSSRC: 5000,
				MediaSSRC:  6000,
			},
		},
	} {
		data, err := test.Packet.Marshal()
		assert.ErrorIsf(t, err, test.WantError, "Marshal %q", test.Name)
		if err != nil {
			continue
		}

		var decoded PictureLossIndication
		assert.NoErrorf(t, decoded.Unmarshal(data), "Unmarshal %q", test.Name)
		assert.Equalf(t, test.Packet, decoded, "%q rr round trip mismatch", test.Name)
	}
}

func TestPictureLossIndicationUnmarshalHeader(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Data      []byte
		Want      Header
		WantError error
	}{
		{
			Name: "valid header",
			Data: []byte{
				// v=2, p=0, FMT=1, PSFB, len=1
				0x81, 0xce, 0x00, 0x02,
				// ssrc=0x0
				0x00, 0x00, 0x00, 0x00,
				// ssrc=0x4bc4fcb4
				0x4b, 0xc4, 0xfc, 0xb4,
			},
			Want: Header{
				Count:  FormatPLI,
				Type:   TypePayloadSpecificFeedback,
				Length: pliLength,
			},
		},
	} {
		var pli PictureLossIndication
		err := pli.Unmarshal(test.Data)
		assert.ErrorIsf(t, err, test.WantError, "Unmarshal header %q", test.Name)
		if err != nil {
			continue
		}

		assert.Equalf(t, test.Want, pli.Header(), "Unmarshal header %q", test.Name)
	}
}


================================================
FILE: rapid_resynchronization_request.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"encoding/binary"
	"fmt"
)

// The RapidResynchronizationRequest packet informs the encoder about the loss of
// an undefined amount of coded video data belonging to one or more pictures.
type RapidResynchronizationRequest struct {
	// SSRC of sender
	SenderSSRC uint32

	// SSRC of the media source
	MediaSSRC uint32
}

// RapidResynchronisationRequest is provided as RFC 6051 spells resynchronization with an s.
// We provide both names to be consistent with other RFCs which spell resynchronization with a z.
type RapidResynchronisationRequest = RapidResynchronizationRequest

const (
	rrrLength       = 2
	rrrHeaderLength = ssrcLength * 2
	rrrMediaOffset  = 4
)

// Marshal encodes the RapidResynchronizationRequest in binary.
func (p RapidResynchronizationRequest) Marshal() ([]byte, error) {
	/*
	 * RRR does not require parameters.  Therefore, the length field MUST be
	 * 2, and there MUST NOT be any Feedback Control Information.
	 *
	 * The semantics of this FB message is independent of the payload type.
	 */
	rawPacket := make([]byte, p.MarshalSize())
	packetBody := rawPacket[headerLength:]

	binary.BigEndian.PutUint32(packetBody, p.SenderSSRC)
	binary.BigEndian.PutUint32(packetBody[rrrMediaOffset:], p.MediaSSRC)

	hData, err := p.Header().Marshal()
	if err != nil {
		return nil, err
	}
	copy(rawPacket, hData)

	return rawPacket, nil
}

// Unmarshal decodes the RapidResynchronizationRequest from binary.
func (p *RapidResynchronizationRequest) Unmarshal(rawPacket []byte) error {
	if len(rawPacket) < (headerLength + (ssrcLength * 2)) {
		return errPacketTooShort
	}

	var h Header
	if err := h.Unmarshal(rawPacket); err != nil {
		return err
	}

	if h.Type != TypeTransportSpecificFeedback || h.Count != FormatRRR {
		return errWrongType
	}

	p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
	p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])

	return nil
}

// MarshalSize returns the size of the packet once marshaled.
func (p *RapidResynchronizationRequest) MarshalSize() int {
	return headerLength + rrrHeaderLength
}

// Header returns the Header associated with this packet.
func (p *RapidResynchronizationRequest) Header() Header {
	return Header{
		Count:  FormatRRR,
		Type:   TypeTransportSpecificFeedback,
		Length: rrrLength,
	}
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *RapidResynchronizationRequest) DestinationSSRC() []uint32 {
	return []uint32{p.MediaSSRC}
}

func (p *RapidResynchronizationRequest) String() string {
	return fmt.Sprintf("RapidResynchronizationRequest %x %x", p.SenderSSRC, p.MediaSSRC)
}


================================================
FILE: rapid_resynchronization_request_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

var _ Packet = (*RapidResynchronizationRequest)(nil) // assert is a Packet

func TestRapidResynchronizationRequestUnmarshal(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Data      []byte
		Want      RapidResynchronizationRequest
		WantError error
	}{
		{
			Name: "valid",
			Data: []byte{
				// RapidResynchronizationRequest
				0x85, 0xcd, 0x0, 0x2,
				// sender=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// media=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
			},
			Want: RapidResynchronizationRequest{
				SenderSSRC: 0x902f9e2e,
				MediaSSRC:  0x902f9e2e,
			},
		},
		{
			Name: "short report",
			Data: []byte{
				0x85, 0xcd, 0x0, 0x2,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// report ends early
			},
			WantError: errPacketTooShort,
		},
		{
			Name: "wrong type",
			Data: []byte{
				// v=2, p=0, count=1, SR, len=7
				0x81, 0xc8, 0x0, 0x7,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// ssrc=0xbc5e9a40
				0xbc, 0x5e, 0x9a, 0x40,
				// fracLost=0, totalLost=0
				0x0, 0x0, 0x0, 0x0,
				// lastSeq=0x46e1
				0x0, 0x0, 0x46, 0xe1,
				// jitter=273
				0x0, 0x0, 0x1, 0x11,
				// lsr=0x9f36432
				0x9, 0xf3, 0x64, 0x32,
				// delay=150137
				0x0, 0x2, 0x4a, 0x79,
			},
			WantError: errWrongType,
		},
		{
			Name:      "nil",
			Data:      nil,
			WantError: errPacketTooShort,
		},
	} {
		var rrr RapidResynchronizationRequest
		err := rrr.Unmarshal(test.Data)
		assert.ErrorIsf(t, err, test.WantError, "Unmarshal %q", test.Name)
		if err != nil {
			continue
		}
		assert.Equalf(t, test.Want, rrr, "Unmarshal %q", test.Name)
	}
}

func TestRapidResynchronizationRequestRoundTrip(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Report    RapidResynchronizationRequest
		WantError error
	}{
		{
			Name: "valid",
			Report: RapidResynchronizationRequest{
				SenderSSRC: 0x902f9e2e,
				MediaSSRC:  0x902f9e2e,
			},
		},
	} {
		data, err := test.Report.Marshal()
		assert.ErrorIsf(t, err, test.WantError, "Marshal %q", test.Name)
		if err != nil {
			continue
		}

		var decoded RapidResynchronizationRequest
		assert.NoErrorf(t, decoded.Unmarshal(data), "Unmarshal %q", test.Name)
		assert.Equalf(t, test.Report, decoded, "%q round trip", test.Name)
	}
}


================================================
FILE: raw_packet.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import "fmt"

// RawPacket represents an unparsed RTCP packet. It's returned by Unmarshal when
// a packet with an unknown type is encountered.
type RawPacket []byte

// Marshal encodes the packet in binary.
func (r RawPacket) Marshal() ([]byte, error) {
	return r, nil
}

// Unmarshal decodes the packet from binary.
func (r *RawPacket) Unmarshal(b []byte) error {
	if len(b) < (headerLength) {
		return errPacketTooShort
	}
	*r = b

	var h Header

	return h.Unmarshal(b)
}

// Header returns the Header associated with this packet.
func (r RawPacket) Header() Header {
	var h Header
	if err := h.Unmarshal(r); err != nil {
		return Header{}
	}

	return h
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (r *RawPacket) DestinationSSRC() []uint32 {
	return []uint32{}
}

func (r RawPacket) String() string {
	out := fmt.Sprintf("RawPacket: %v", ([]byte)(r))

	return out
}

// MarshalSize returns the size of the packet once marshaled.
func (r RawPacket) MarshalSize() int {
	return len(r)
}


================================================
FILE: raw_packet_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

var _ Packet = (*RawPacket)(nil) // assert is a Packet

func TestRawPacketRoundTrip(t *testing.T) {
	for _, test := range []struct {
		Name               string
		Packet             RawPacket
		WantMarshalError   error
		WantUnmarshalError error
	}{
		{
			Name: "valid",
			Packet: RawPacket([]byte{
				// v=2, p=0, count=1, BYE, len=12
				0x81, 0xcb, 0x00, 0x0c,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// len=3, text=FOO
				0x03, 0x46, 0x4f, 0x4f,
			}),
		},
		{
			Name:               "short header",
			Packet:             RawPacket([]byte{0x00}),
			WantUnmarshalError: errPacketTooShort,
		},
		{
			Name: "invalid header",
			Packet: RawPacket([]byte{
				// v=0, p=0, count=0, RR, len=4
				0x00, 0xc9, 0x00, 0x04,
			}),
			WantUnmarshalError: errBadVersion,
		},
	} {
		data, err := test.Packet.Marshal()
		assert.ErrorIsf(t, err, test.WantMarshalError, "Marshal %q", test.Name)
		if err != nil {
			continue
		}

		var decoded RawPacket

		err = decoded.Unmarshal(data)
		assert.ErrorIsf(t, err, test.WantUnmarshalError, "Unmarshal %q", test.Name)
		if err != nil {
			continue
		}
		assert.Equalf(t, test.Packet, decoded, "Unmarshal %q", test.Name)
	}
}


================================================
FILE: receiver_estimated_maximum_bitrate.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math"
)

// ReceiverEstimatedMaximumBitrate contains the receiver's estimated maximum bitrate.
// see: https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03
type ReceiverEstimatedMaximumBitrate struct {
	// SSRC of sender
	SenderSSRC uint32

	// Estimated maximum bitrate
	Bitrate float32

	// SSRC entries which this packet applies to
	SSRCs []uint32
}

// Marshal serializes the packet and returns a byte slice.
func (p ReceiverEstimatedMaximumBitrate) Marshal() (buf []byte, err error) {
	// Allocate a buffer of the exact output size.
	buf = make([]byte, p.MarshalSize())

	// Write to our buffer.
	n, err := p.MarshalTo(buf)
	if err != nil {
		return nil, err
	}

	// This will always be true but just to be safe.
	if n != len(buf) {
		return nil, errWrongMarshalSize
	}

	return buf, nil
}

// MarshalSize returns the size of the packet once marshaled.
func (p ReceiverEstimatedMaximumBitrate) MarshalSize() int {
	return 20 + 4*len(p.SSRCs)
}

// MarshalTo serializes the packet to the given byte slice.
func (p ReceiverEstimatedMaximumBitrate) MarshalTo(buf []byte) (n int, err error) {
	const bitratemax = 0x3FFFFp+63
	/*
	    0                   1                   2                   3
	    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
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |V=2|P| FMT=15  |   PT=206      |             length            |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |                  SSRC of packet sender                        |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |                  SSRC of media source                         |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |  Unique identifier 'R' 'E' 'M' 'B'                            |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |  Num SSRC     | BR Exp    |  BR Mantissa                      |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |   SSRC feedback                                               |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |  ...                                                          |
	*/

	size := p.MarshalSize()
	if len(buf) < size {
		return 0, errPacketTooShort
	}

	buf[0] = 143 // v=2, p=0, fmt=15
	buf[1] = 206

	// Length of this packet in 32-bit words minus one.
	length := uint16((p.MarshalSize() / 4) - 1) //nolint:gosec // G115
	binary.BigEndian.PutUint16(buf[2:4], length)

	binary.BigEndian.PutUint32(buf[4:8], p.SenderSSRC)
	binary.BigEndian.PutUint32(buf[8:12], 0) // always zero

	// ALL HAIL REMB
	buf[12] = 'R'
	buf[13] = 'E'
	buf[14] = 'M'
	buf[15] = 'B'

	// Write the length of the ssrcs to follow at the end
	if len(p.SSRCs) > math.MaxUint8 {
		return 0, errTooManySSRCs
	}

	buf[16] = byte(len(p.SSRCs)) //nolint:gosec // length validated above

	exp := 0
	bitrate := p.Bitrate

	if bitrate >= bitratemax {
		bitrate = bitratemax
	}

	if bitrate < 0 {
		return 0, errInvalidBitrate
	}

	for bitrate >= (1 << 18) {
		bitrate /= 2.0
		exp++
	}

	if exp >= (1 << 6) {
		return 0, errInvalidBitrate
	}

	mantissa := uint(math.Floor(float64(bitrate)))

	// We can't quite use the binary package because
	// a) it's a uint24 and b) the exponent is only 6-bits
	// Just trust me; this is big-endian encoding.
	buf[17] = byte(exp<<2) | byte(mantissa>>16) //nolint: gosec //  mantissa is limited to 18 bits
	buf[18] = byte(mantissa >> 8)               //nolint: gosec //  mantissa is limited to 18 bits
	buf[19] = byte(mantissa)                    // nolint: gosec //  mantissa is limited to 18 bits

	// Write the SSRCs at the very end.
	n = 20
	for _, ssrc := range p.SSRCs {
		binary.BigEndian.PutUint32(buf[n:n+4], ssrc)
		n += 4
	}

	return n, nil
}

// Unmarshal reads a REMB packet from the given byte slice.
//
//nolint:cyclop
func (p *ReceiverEstimatedMaximumBitrate) Unmarshal(buf []byte) (err error) {
	const mantissamax = 0x7FFFFF
	/*
	    0                   1                   2                   3
	    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
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |V=2|P| FMT=15  |   PT=206      |             length            |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |                  SSRC of packet sender                        |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |                  SSRC of media source                         |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |  Unique identifier 'R' 'E' 'M' 'B'                            |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |  Num SSRC     | BR Exp    |  BR Mantissa                      |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |   SSRC feedback                                               |
	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	   |  ...                                                          |
	*/

	// 20 bytes is the size of the packet with no SSRCs
	if len(buf) < 20 {
		return errPacketTooShort
	}

	// version  must be 2
	version := buf[0] >> 6
	if version != 2 {
		return fmt.Errorf("%w expected(2) actual(%d)", errBadVersion, version)
	}

	// padding must be unset
	padding := (buf[0] >> 5) & 1
	if padding != 0 {
		return fmt.Errorf("%w expected(0) actual(%d)", errWrongPadding, padding)
	}

	// fmt must be 15
	fmtVal := buf[0] & 31
	if fmtVal != 15 {
		return fmt.Errorf("%w expected(15) actual(%d)", errWrongFeedbackType, fmtVal)
	}

	// Must be payload specific feedback
	if buf[1] != 206 {
		return fmt.Errorf("%w expected(206) actual(%d)", errWrongPayloadType, buf[1])
	}

	// length is the number of 32-bit words, minus 1
	length := binary.BigEndian.Uint16(buf[2:4])
	size := int((length + 1) * 4)

	// There's not way this could be legit
	if size < 20 {
		return errHeaderTooSmall
	}

	// Make sure the buffer is large enough.
	if len(buf) < size {
		return errPacketTooShort
	}

	// The sender SSRC is 32-bits
	p.SenderSSRC = binary.BigEndian.Uint32(buf[4:8])

	// The destination SSRC must be 0
	media := binary.BigEndian.Uint32(buf[8:12])
	if media != 0 {
		return errSSRCMustBeZero
	}

	// REMB rules all around me
	if !bytes.Equal(buf[12:16], []byte{'R', 'E', 'M', 'B'}) {
		return errMissingREMBidentifier
	}

	// The next byte is the number of SSRC entries at the end.
	num := int(buf[16])

	// Now we know the expected size, make sure they match.
	if size != 20+4*num {
		return errSSRCNumAndLengthMismatch
	}

	// Get the 6-bit exponent value.
	exp := buf[17] >> 2
	exp += 127 // bias for IEEE754
	exp += 23  // IEEE754 biases the decimal to the left, abs-send-time biases it to the right

	// The remaining 2-bits plus the next 16-bits are the mantissa.
	mantissa := uint32(buf[17]&3)<<16 | uint32(buf[18])<<8 | uint32(buf[19])

	if mantissa != 0 {
		// ieee754 requires an implicit leading bit
		for (mantissa & (mantissamax + 1)) == 0 {
			exp--
			mantissa *= 2
		}
	}

	// bitrate = mantissa * 2^exp
	p.Bitrate = math.Float32frombits((uint32(exp) << 23) | (mantissa & mantissamax))

	// Clear any existing SSRCs
	p.SSRCs = nil

	// Loop over and parse the SSRC entires at the end.
	// We already verified that size == num * 4
	for n := 20; n < size; n += 4 {
		ssrc := binary.BigEndian.Uint32(buf[n : n+4])
		p.SSRCs = append(p.SSRCs, ssrc)
	}

	return nil
}

// Header returns the Header associated with this packet.
func (p *ReceiverEstimatedMaximumBitrate) Header() Header {
	return Header{
		Count:  FormatREMB,
		Type:   TypePayloadSpecificFeedback,
		Length: uint16((p.MarshalSize() / 4) - 1), //nolint:gosec // G115
	}
}

// String prints the REMB packet in a human-readable format.
func (p *ReceiverEstimatedMaximumBitrate) String() string {
	// Keep a table of powers to units for fast conversion.
	bitUnits := []string{"b", "Kb", "Mb", "Gb", "Tb", "Pb", "Eb"}

	// Do some unit conversions because b/s is far too difficult to read.
	bitrate := p.Bitrate
	powers := 0

	// Keep dividing the bitrate until it's under 1000
	for bitrate >= 1000.0 && powers < len(bitUnits) {
		bitrate /= 1000.0
		powers++
	}

	unit := bitUnits[powers] //nolint:gosec // powers is bounded by loop condition

	return fmt.Sprintf("ReceiverEstimatedMaximumBitrate %x %.2f %s/s", p.SenderSSRC, bitrate, unit)
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *ReceiverEstimatedMaximumBitrate) DestinationSSRC() []uint32 {
	return p.SSRCs
}


================================================
FILE: receiver_estimated_maximum_bitrate_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"math"
	"testing"

	"github.com/stretchr/testify/assert"
)

var _ Packet = (*ReceiverEstimatedMaximumBitrate)(nil) // assert is a Packet

func TestReceiverEstimatedMaximumBitrateMarshal(t *testing.T) {
	assert := assert.New(t)

	input := ReceiverEstimatedMaximumBitrate{
		SenderSSRC: 1,
		Bitrate:    8927168.0,
		SSRCs:      []uint32{1215622422},
	}

	expected := []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}

	output, err := input.Marshal()
	assert.NoError(err)
	assert.Equal(expected, output)
}

func TestReceiverEstimatedMaximumBitrateUnmarshal(t *testing.T) {
	assert := assert.New(t)

	// Real data sent by Chrome while watching a 6Mb/s stream
	input := []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}

	// mantissa = []byte{26 & 3, 32, 223} = []byte{2, 32, 223} = 139487
	// exp = 26 >> 2 = 6
	// bitrate = 139487 * 2^6 = 139487 * 64 = 8927168 = 8.9 Mb/s
	expected := ReceiverEstimatedMaximumBitrate{
		SenderSSRC: 1,
		Bitrate:    8927168,
		SSRCs:      []uint32{1215622422},
	}

	packet := ReceiverEstimatedMaximumBitrate{}
	err := packet.Unmarshal(input)
	assert.NoError(err)
	assert.Equal(expected, packet)
}

func TestReceiverEstimatedMaximumBitrateTruncate(t *testing.T) {
	assert := assert.New(t)

	input := []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}

	// Make sure that we're interpreting the bitrate correctly.
	// For the above example, we have:

	// mantissa = 139487
	// exp = 6
	// bitrate = 8927168

	packet := ReceiverEstimatedMaximumBitrate{}
	err := packet.Unmarshal(input)
	assert.NoError(err)
	assert.Equal(float32(8927168), packet.Bitrate)

	// Just verify marshal produces the same input.
	output, err := packet.Marshal()
	assert.NoError(err)
	assert.Equal(input, output)

	// If we subtract the bitrate by 1, we'll round down a lower mantissa
	packet.Bitrate--

	// bitrate = 8927167
	// mantissa = 139486
	// exp = 6

	output, err = packet.Marshal()
	assert.NoError(err)
	assert.NotEqual(input, output)

	// Which if we actually unmarshal again, we'll find that it's actually decreased by 63 (which is exp)
	// mantissa = 139486
	// exp = 6
	// bitrate = 8927104

	err = packet.Unmarshal(output)
	assert.NoError(err)
	assert.Equal(float32(8927104), packet.Bitrate)
}

func TestReceiverEstimatedMaximumBitrateOverflow(t *testing.T) {
	assert := assert.New(t)

	// Marshal a packet with the maximum possible bitrate.
	packet := ReceiverEstimatedMaximumBitrate{
		Bitrate: math.MaxFloat32,
	}

	// mantissa = 262143 = 0x3FFFF
	// exp = 63

	expected := []byte{143, 206, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 82, 69, 77, 66, 0, 255, 255, 255}

	output, err := packet.Marshal()
	assert.NoError(err)
	assert.Equal(expected, output)

	// mantissa = 262143
	// exp = 63
	// bitrate = 0xFFFFC00000000000

	err = packet.Unmarshal(output)
	assert.NoError(err)
	assert.Equal(math.Float32frombits(0x67FFFFC0), packet.Bitrate)

	// Make sure we marshal to the same result again.
	output, err = packet.Marshal()
	assert.NoError(err)
	assert.Equal(expected, output)

	// Finally, try unmarshalling one number higher than we used to be able to handle.
	input := []byte{143, 206, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 82, 69, 77, 66, 0, 188, 0, 0}
	err = packet.Unmarshal(input)
	assert.NoError(err)
	assert.Equal(math.Float32frombits(0x62800000), packet.Bitrate)
}


================================================
FILE: receiver_report.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"encoding/binary"
	"fmt"
	"strings"
)

// A ReceiverReport (RR) packet provides reception quality feedback for an RTP stream.
type ReceiverReport struct {
	// The synchronization source identifier for the originator of this RR packet.
	SSRC uint32
	// Zero or more reception report blocks depending on the number of other
	// sources heard by this sender since the last report. Each reception report
	// block conveys statistics on the reception of RTP packets from a
	// single synchronization source.
	Reports []ReceptionReport
	// Extension contains additional, payload-specific information that needs to
	// be reported regularly about the receiver.
	ProfileExtensions []byte
}

const (
	ssrcLength     = 4
	rrSSRCOffset   = headerLength
	rrReportOffset = rrSSRCOffset + ssrcLength
)

// Marshal encodes the ReceiverReport in binary.
func (r ReceiverReport) Marshal() ([]byte, error) {
	/*
	 *         0                   1                   2                   3
	 *         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
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * header |V=2|P|    RC   |   PT=RR=201   |             length            |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                     SSRC of packet sender                     |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * report |                 SSRC_1 (SSRC of first source)                 |
	 * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *   1    | fraction lost |       cumulative number of packets lost       |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |           extended highest sequence number received           |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                      interarrival jitter                      |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                         last SR (LSR)                         |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                   delay since last SR (DLSR)                  |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * report |                 SSRC_2 (SSRC of second source)                |
	 * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *   2    :                               ...                             :
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 *        |                  profile-specific extensions                  |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	rawPacket := make([]byte, r.MarshalSize())
	packetBody := rawPacket[headerLength:]

	binary.BigEndian.PutUint32(packetBody, r.SSRC)

	for i, rp := range r.Reports {
		data, err := rp.Marshal()
		if err != nil {
			return nil, err
		}
		offset := ssrcLength + receptionReportLength*i
		copy(packetBody[offset:], data)
	}

	if len(r.Reports) > countMax {
		return nil, errTooManyReports
	}

	pe := make([]byte, len(r.ProfileExtensions))
	copy(pe, r.ProfileExtensions)

	// if the length of the profile extensions isn't devisible
	// by 4, we need to pad the end.
	for (len(pe) & 0x3) != 0 {
		pe = append(pe, 0) //nolint:makezero
	}

	rawPacket = append(rawPacket, pe...) //nolint:makezero

	hData, err := r.Header().Marshal()
	if err != nil {
		return nil, err
	}
	copy(rawPacket, hData)

	return rawPacket, nil
}

// Unmarshal decodes the ReceiverReport from binary.
func (r *ReceiverReport) Unmarshal(rawPacket []byte) error {
	/*
	 *         0                   1                   2                   3
	 *         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
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * header |V=2|P|    RC   |   PT=RR=201   |             length            |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                     SSRC of packet sender                     |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * report |                 SSRC_1 (SSRC of first source)                 |
	 * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *   1    | fraction lost |       cumulative number of packets lost       |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |           extended highest sequence number received           |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                      interarrival jitter                      |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                         last SR (LSR)                         |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                   delay since last SR (DLSR)                  |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * report |                 SSRC_2 (SSRC of second source)                |
	 * block  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *   2    :                               ...                             :
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 *        |                  profile-specific extensions                  |
	 *        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	if len(rawPacket) < (headerLength + ssrcLength) {
		return errPacketTooShort
	}

	var header Header
	if err := header.Unmarshal(rawPacket); err != nil {
		return err
	}

	if header.Type != TypeReceiverReport {
		return errWrongType
	}

	r.SSRC = binary.BigEndian.Uint32(rawPacket[rrSSRCOffset:])

	for i := rrReportOffset; i < len(rawPacket) && len(r.Reports) < int(header.Count); i += receptionReportLength {
		var rr ReceptionReport
		if err := rr.Unmarshal(rawPacket[i:]); err != nil {
			return err
		}
		r.Reports = append(r.Reports, rr)
	}
	r.ProfileExtensions = rawPacket[rrReportOffset+(len(r.Reports)*receptionReportLength):]

	//nolint:gosec // G115
	if uint8(len(r.Reports)) != header.Count {
		return errInvalidHeader
	}

	return nil
}

// MarshalSize returns the size of the packet once marshaled.
func (r *ReceiverReport) MarshalSize() int {
	repsLength := 0
	for _, rep := range r.Reports {
		repsLength += rep.len()
	}

	return headerLength + ssrcLength + repsLength
}

// Header returns the Header associated with this packet.
func (r *ReceiverReport) Header() Header {
	return Header{
		Count:  uint8(len(r.Reports)), //nolint:gosec // G115
		Type:   TypeReceiverReport,
		Length: uint16((r.MarshalSize()/4)-1) + uint16(getPadding(len(r.ProfileExtensions))), //nolint:gosec // G115
	}
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (r *ReceiverReport) DestinationSSRC() []uint32 {
	out := make([]uint32, len(r.Reports))
	for i, v := range r.Reports {
		out[i] = v.SSRC
	}

	return out
}

func (r ReceiverReport) String() string {
	var out strings.Builder
	fmt.Fprintf(&out, "ReceiverReport from %x\n", r.SSRC)
	out.WriteString("\tSSRC    \tLost\tLastSequence\n")
	for _, i := range r.Reports {
		fmt.Fprintf(&out, "\t%x\t%d/%d\t%d\n", i.SSRC, i.FractionLost, i.TotalLost, i.LastSequenceNumber)
	}
	fmt.Fprintf(&out, "\tProfile Extension Data: %v\n", r.ProfileExtensions)

	return out.String()
}


================================================
FILE: receiver_report_test.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

var _ Packet = (*ReceiverReport)(nil) // assert is a Packet

func TestReceiverReportUnmarshal(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Data      []byte
		Want      ReceiverReport
		WantError error
	}{
		{
			Name: "valid",
			Data: []byte{
				// v=2, p=0, count=1, RR, len=7
				0x81, 0xc9, 0x0, 0x7,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// ssrc=0xbc5e9a40
				0xbc, 0x5e, 0x9a, 0x40,
				// fracLost=0, totalLost=0
				0x0, 0x0, 0x0, 0x0,
				// lastSeq=0x46e1
				0x0, 0x0, 0x46, 0xe1,
				// jitter=273
				0x0, 0x0, 0x1, 0x11,
				// lsr=0x9f36432
				0x9, 0xf3, 0x64, 0x32,
				// delay=150137
				0x0, 0x2, 0x4a, 0x79,
			},
			Want: ReceiverReport{
				SSRC: 0x902f9e2e,
				Reports: []ReceptionReport{{
					SSRC:               0xbc5e9a40,
					FractionLost:       0,
					TotalLost:          0,
					LastSequenceNumber: 0x46e1,
					Jitter:             273,
					LastSenderReport:   0x9f36432,
					Delay:              150137,
				}},
				ProfileExtensions: []byte{},
			},
		},
		{
			Name: "valid with extension data",
			Data: []byte{
				// v=2, p=0, count=1, RR, len=9
				0x81, 0xc9, 0x0, 0x9,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// ssrc=0xbc5e9a40
				0xbc, 0x5e, 0x9a, 0x40,
				// fracLost=0, totalLost=0
				0x0, 0x0, 0x0, 0x0,
				// lastSeq=0x46e1
				0x0, 0x0, 0x46, 0xe1,
				// jitter=273
				0x0, 0x0, 0x1, 0x11,
				// lsr=0x9f36432
				0x9, 0xf3, 0x64, 0x32,
				// delay=150137
				0x0, 0x2, 0x4a, 0x79,
				// profile-specific extension data
				0x54, 0x45, 0x53, 0x54,
				0x44, 0x41, 0x54, 0x41,
			},
			Want: ReceiverReport{
				SSRC: 0x902f9e2e,
				Reports: []ReceptionReport{{
					SSRC:               0xbc5e9a40,
					FractionLost:       0,
					TotalLost:          0,
					LastSequenceNumber: 0x46e1,
					Jitter:             273,
					LastSenderReport:   0x9f36432,
					Delay:              150137,
				}},
				ProfileExtensions: []byte{
					0x54, 0x45, 0x53, 0x54,
					0x44, 0x41, 0x54, 0x41,
				},
			},
		},
		{
			Name: "short report",
			Data: []byte{
				// v=2, p=0, count=1, RR, len=7
				0x81, 0xc9, 0x00, 0x0c,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// fracLost=0, totalLost=0
				0x00, 0x00, 0x00, 0x00,
				// report ends early
			},
			WantError: errPacketTooShort,
		},
		{
			Name: "wrong type",
			Data: []byte{
				// v=2, p=0, count=1, SR, len=7
				0x81, 0xc8, 0x0, 0x7,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// ssrc=0xbc5e9a40
				0xbc, 0x5e, 0x9a, 0x40,
				// fracLost=0, totalLost=0
				0x0, 0x0, 0x0, 0x0,
				// lastSeq=0x46e1
				0x0, 0x0, 0x46, 0xe1,
				// jitter=273
				0x0, 0x0, 0x1, 0x11,
				// lsr=0x9f36432
				0x9, 0xf3, 0x64, 0x32,
				// delay=150137
				0x0, 0x2, 0x4a, 0x79,
			},
			WantError: errWrongType,
		},
		{
			Name: "bad count in header",
			Data: []byte{
				// v=2, p=0, count=2, RR, len=7
				0x82, 0xc9, 0x0, 0x7,
				// ssrc=0x902f9e2e
				0x90, 0x2f, 0x9e, 0x2e,
				// ssrc=0xbc5e9a40
				0xbc, 0x5e, 0x9a, 0x40,
				// fracLost=0, totalLost=0
				0x0, 0x0, 0x0, 0x0,
				// lastSeq=0x46e1
				0x0, 0x0, 0x46, 0xe1,
				// jitter=273
				0x0, 0x0, 0x1, 0x11,
				// lsr=0x9f36432
				0x9, 0xf3, 0x64, 0x32,
				// delay=150137
				0x0, 0x2, 0x4a, 0x79,
			},
			WantError: errInvalidHeader,
		},
		{
			Name:      "nil",
			Data:      nil,
			WantError: errPacketTooShort,
		},
	} {
		var rr ReceiverReport
		err := rr.Unmarshal(test.Data)
		assert.ErrorIsf(t, err, test.WantError, "Unmarshal %q", test.Name)
		if err != nil {
			continue
		}

		assert.Equalf(t, test.Want, rr, "Unmarshal %q", test.Name)
	}
}

func tooManyReports() []ReceptionReport {
	// a slice with enough ReceptionReports to overflow an 5-bit int
	var tooManyReports []ReceptionReport
	for range 1 << 5 {
		tooManyReports = append(tooManyReports, ReceptionReport{
			SSRC:               2,
			FractionLost:       2,
			TotalLost:          3,
			LastSequenceNumber: 4,
			Jitter:             5,
			LastSenderReport:   6,
			Delay:              7,
		})
	}

	return tooManyReports
}

func TestReceiverReportRoundTrip(t *testing.T) {
	for _, test := range []struct {
		Name      string
		Report    ReceiverReport
		WantError error
	}{
		{
			Name: "valid",
			Report: ReceiverReport{
				SSRC: 1,
				Reports: []ReceptionReport{
					{
						SSRC:               2,
						FractionLost:       2,
						TotalLost:          3,
						LastSequenceNumber: 4,
						Jitter:             5,
						LastSenderReport:   6,
						Delay:              7,
					},
					{
						SSRC: 0,
					},
				},
				ProfileExtensions: []byte{},
			},
		},
		{
			Name: "also valid",
			Report: ReceiverReport{
				SSRC: 2,
				Reports: []ReceptionReport{
					{
						SSRC:               999,
						FractionLost:       30,
						TotalLost:          12345,
						LastSequenceNumber: 99,
						Jitter:             22,
						LastSenderReport:   92,
						Delay:              46,
					},
				},
				ProfileExtensions: []byte{},
			},
		},
		{
			Name: "totallost overflow",
			Report: ReceiverReport{
				SSRC: 1,
				Reports: []ReceptionReport{{
					TotalLost: 1 << 25,
				}},
			},
			WantError: errInvalidTotalLost,
		},
		{
			Name: "count overflow",
			Report: ReceiverReport{
				SSRC:    1,
				Reports: tooManyReports(),
			},
			WantError: errTooManyReports,
		},
	} {
		data, err := test.Report.Marshal()
		assert.ErrorIsf(t, err, test.WantError, "Marshal %q", test.Name)
		if err != nil {
			continue
		}

		var decoded ReceiverReport
		assert.NoErrorf(t, decoded.Unmarshal(data), "Unmarshal %q", test.Name)
		assert.Equalf(t, test.Report, decoded, "%s rr round trip mismatch", test.Name)
	}
}


================================================
FILE: reception_report.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import "encoding/binary"

// A ReceptionReport block conveys statistics on the reception of RTP packets
// from a single synchronization source.
type ReceptionReport struct {
	// The SSRC identifier of the source to which the information in this
	// reception report block pertains.
	SSRC uint32
	// The fraction of RTP data packets from source SSRC lost since the
	// previous SR or RR packet was sent, expressed as a fixed point
	// number with the binary point at the left edge of the field.
	FractionLost uint8
	// The total number of RTP data packets from source SSRC that have
	// been lost since the beginning of reception.
	TotalLost uint32
	// The low 16 bits contain the highest sequence number received in an
	// RTP data packet from source SSRC, and the most significant 16
	// bits extend that sequence number with the corresponding count of
	// sequence number cycles.
	LastSequenceNumber uint32
	// An estimate of the statistical variance of the RTP data packet
	// interarrival time, measured in timestamp units and expressed as an
	// unsigned integer.
	Jitter uint32
	// The middle 32 bits out of 64 in the NTP timestamp received as part of
	// the most recent RTCP sender report (SR) packet from source SSRC. If no
	// SR has been received yet, the field is set to zero.
	LastSenderReport uint32
	// The delay, expressed in units of 1/65536 seconds, between receiving the
	// last SR packet from source SSRC and sending this reception report block.
	// If no SR packet has been received yet from SSRC, the field is set to zero.
	Delay uint32
}

const (
	receptionReportLength = 24
	fractionLostOffset    = 4
	totalLostOffset       = 5
	lastSeqOffset         = 8
	jitterOffset          = 12
	lastSROffset          = 16
	delayOffset           = 20
)

// Marshal encodes the ReceptionReport in binary.
func (r ReceptionReport) Marshal() ([]byte, error) {
	/*
	 *  0                   1                   2                   3
	 *  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
	 * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * |                              SSRC                             |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * | fraction lost |       cumulative number of packets lost       |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * |           extended highest sequence number received           |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * |                      interarrival jitter                      |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * |                         last SR (LSR)                         |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * |                   delay since last SR (DLSR)                  |
	 * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 */

	rawPacket := make([]byte, receptionReportLength)

	binary.BigEndian.PutUint32(rawPacket, r.SSRC)

	rawPacket[fractionLostOffset] = r.FractionLost

	// pack TotalLost into 24 bits
	if r.TotalLost >= (1 << 25) {
		return nil, errInvalidTotalLost
	}
	tlBytes := rawPacket[totalLostOffset:]
	tlBytes[0] = byte(r.TotalLost >> 16) //nolint:gosec // rawPacket is created with length receptionReportLength (24)
	tlBytes[1] = byte(r.TotalLost >> 8)  //nolint:gosec // rawPacket is created with length receptionReportLength (24)
	tlBytes[2] = byte(r.TotalLost)       //nolint:gosec // rawPacket is created with length receptionReportLength (24)

	binary.BigEndian.PutUint32(rawPacket[lastSeqOffset:], r.LastSequenceNumber)
	binary.BigEndian.PutUint32(rawPacket[jitterOffset:], r.Jitter)
	binary.BigEndian.PutUint32(rawPacket[lastSROffset:], r.LastSenderReport)
	binary.BigEndian.PutUint32(rawPacket[delayOffset:], r.Delay)

	return rawPacket, nil
}

// Unmarshal decodes the ReceptionReport from binary.
func (r *ReceptionReport) Unmarshal(rawPacket []byte) error {
	if len(rawPacket) < receptionReportLength {
		return errPacketTooShort
	}

	/*
	 *  0                   1                   2                   3
	 *  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
	 * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * |                              SSRC                             |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * | fraction lost |       cumulative number of packets lost       |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * |           extended highest sequence number received           |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * |                      interarrival jitter                      |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * |                         last SR (LSR)                         |
	 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 * |                   delay since last SR (DLSR)                  |
	 * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 */

	r.SSRC = binary.BigEndian.Uint32(rawPacket)
	r.FractionLost = rawPacket[fractionLostOffset]

	tlBytes := rawPacket[totalLostOffset:]
	r.TotalLost = uint32(tlBytes[2]) | uint32(tlBytes[1])<<8 | uint32(tlBytes[0])<<16

	r.LastSequenceNumber = binary.BigEndian.Uint32(rawPacket[lastSeqOffset:])
	r.Jitter = binary.BigEndian.Uint32(rawPacket[jitterOffset:])
	r.LastSenderReport = binary.BigEndian.Uint32(rawPacket[lastSROffset:])
	r.Delay = binary.BigEndian.Uint32(rawPacket[delayOffset:])

	return nil
}

func (r *ReceptionReport) len() int {
	return receptionReportLength
}


================================================
FILE: renovate.json
================================================
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "github>pion/renovate-config"
  ]
}


================================================
FILE: rfc8888.go
================================================
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"strings"
)

// https://www.rfc-editor.org/rfc/rfc8888.html#name-rtcp-congestion-control-fee
//  0                   1                   2                   3
//  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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |V=2|P| FMT=11  |   PT = 205    |          length               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                 SSRC of RTCP packet sender                    |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                   SSRC of 1st RTP Stream                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          begin_seq            |          num_reports          |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |R|ECN|  Arrival time offset    | ...                           .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// .                                                               .
// .                                                               .
// .                                                               .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                   SSRC of nth RTP Stream                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |          begin_seq            |          num_reports          |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |R|ECN|  Arrival time offset    | ...                           |
// .                                                               .
// .                                                               .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                 Report Timestamp (32 bits)                    |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

var (
	errReportBlockLength   = errors.New("feedback report blocks must be at least 8 bytes")
	errIncorrectNumReports = errors.New("feedback report block contains less reports than num_reports")
	errMetricBlockLength   = errors.New("feedback report metric blocks must be exactly 2 bytes")
)

// ECN represents the two ECN bits.
type ECN uint8

const (
	//nolint:misspell
	// ECNNonECT signals Non ECN-Capable Transport, Non-ECT.
	ECNNonECT ECN = iota // 00

	//nolint:misspell
	// ECNECT1 signals ECN Capable Transport, ECT(0).
	ECNECT1 // 01

	//nolint:misspell
	// ECNECT0 signals ECN Capable Transport, ECT(1).
	ECNECT0 // 10

	// ECNCE signals ECN Congestion Encountered, CE.
	ECNCE // 11
)

func (e ECN) String() string {
	switch e {
	case ECNNonECT:
		//nolint:misspell
		return "Non-ECT (00)"
	case ECNECT0:
		//nolint:misspell
		return "ECT(0) (01)"
	case ECNECT1:
		//nolint:misspell
		return "ECT(1) (10)"
	case ECNCE:
		//nolint:misspell
		return "CE (11)"
	}

	return "invalid ECN value"
}

const (
	reportTimestampLength = 4
	reportBlockOffset     = 8
)

// CCFeedbackReport is a Congestion Control Feedback Report as defined in
// https://www.rfc-editor.org/rfc/rfc8888.html#name-rtcp-congestion-control-fee
type CCFeedbackReport struct {
	// SSRC of sender
	SenderSSRC uint32

	// Report Blocks
	ReportBlocks []CCFeedbackReportBlock

	// Basetime
	ReportTimestamp uint32
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (b CCFeedbackReport) DestinationSSRC() []uint32 {
	ssrcs := make([]uint32, len(b.ReportBlocks))
	for i, block := range b.ReportBlocks {
		ssrcs[i] = block.MediaSSRC
	}

	return ssrcs
}

// Len returns the length of the report in bytes.
func (b *CCFeedbackReport) Len() int {
	return b.MarshalSize()
}

// MarshalSize returns the size of the packet once marshaled.
func (b *CCFeedbackReport) MarshalSize() int {
	n := 0
	for _, block := range b.ReportBlocks {
		n += block.len()
	}

	return reportBlockOffset + n + reportTimestampLength
}

// Header returns the Header associated with this packet.
func (b *CCFeedbackReport) Header() Header {
	return Header{
		Padding: false,
		Count:   FormatCCFB,
		Type:    TypeTransportSpecificFeedback,
		Length:  uint16(b.MarshalSize()/4 - 1), //nolint:gosec // G115
	}
}

// Marshal encodes the Congestion Control Feedback Report in binary.
func (b CCFeedbackReport) Marshal() ([]byte, error) {
	header := b.Header()
	headerBuf, err := header.Marshal()
	if err != nil {
		return nil, err
	}
	length := 4 * (header.Length + 1)
	buf := make([]byte, length)
	copy(buf[:headerLength], headerBuf)
	binary.BigEndian.PutUint32(buf[headerLength:], b.SenderSSRC)
	offset := reportBlockOffset
	for _, block := range b.ReportBlocks {
		b, err := block.marshal()
		if err != nil {
			return nil, err
		}
		copy(buf[offset:], b)
		offset += block.len()
	}

	binary.BigEndian.PutUint32(buf[offset:], b.ReportTimestamp)

	return buf, nil
}

func (b CCFeedbackReport) String() string {
	var out strings.Builder
	fmt.Fprintf(&out, "CCFB:\n\tHeader %v\n", b.Header())
	fmt.Fprintf(&out, "CCFB:\n\tSender SSRC %d\n", b.SenderSSRC)
	fmt.Fprintf(&out, "\tReport Timestamp %d\n", b.ReportTimestamp)
	out.WriteString("\tFeedback Reports \n")
	for _, report := range b.ReportBlocks {
		fmt.Fprintf(&out, "%v ", report)
	}
	out.WriteString("\n")

	return out.String()
}

// Unmarshal decodes the Congestion Control Feedback Report from binary.
func (b *CCFeedbackReport) Unmarshal(rawPacket []byte) error {
	if len(rawPacket) < headerLength+ssrcLength+reportTimestampLength {
		return errPacketTooShort
	}

	var h Header
	if err := h.Unmarshal(rawPacket); err != nil {
		return err
	}
	if h.Type != TypeTransportSpecificFeedback {
		return errWrongType
	}

	b.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])

	reportTimestampOffset := len(rawPacket) - reportTimestampLength
	b.ReportTimestamp = binary.BigEndian.Uint32(rawPacket[reportTimestampOffset:])

	offset := reportBlockOffset
	b.ReportBlocks = []CCFeedbackReportBlock{}
	for offset < reportTimestampOffset {
		var block CCFeedbackReportBlock
		if err := block.unmarshal(rawPacket[offset:]); err != nil {
			return err
		}
		b.ReportBlocks = append(b.ReportBlocks, block)
		offset += block.len()
	}

	return nil
}

const (
	ssrcOffset          = 0
	beginSequenceOffset = 4
	numReportsOffset    = 6
	reportsOffset       = 8

	maxMetricBlocks = 16384
)

// CCFeedbackReportBlock is a Feedback Report Block.
type CCFeedbackReportBlock struct {
	// SSRC of the RTP stream on which this b
Download .txt
gitextract_env3fvgh/

├── .github/
│   ├── .gitignore
│   ├── fetch-scripts.sh
│   ├── install-hooks.sh
│   └── workflows/
│       ├── api.yaml
│       ├── codeql-analysis.yml
│       ├── fuzz.yaml
│       ├── lint.yaml
│       ├── release.yml
│       ├── renovate-go-sum-fix.yaml
│       ├── reuse.yml
│       ├── test.yaml
│       └── tidy-check.yaml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .reuse/
│   └── dep5
├── LICENSE
├── LICENSES/
│   ├── CC0-1.0.txt
│   └── MIT.txt
├── README.md
├── application_defined.go
├── application_defined_test.go
├── codecov.yml
├── compound_packet.go
├── compound_packet_test.go
├── doc.go
├── errors.go
├── extended_report.go
├── extended_report_test.go
├── full_intra_request.go
├── full_intra_request_test.go
├── fuzz_test.go
├── go.mod
├── go.sum
├── goodbye.go
├── goodbye_test.go
├── header.go
├── header_test.go
├── packet.go
├── packet_buffer.go
├── packet_buffer_test.go
├── packet_stringifier.go
├── packet_stringifier_test.go
├── packet_test.go
├── picture_loss_indication.go
├── picture_loss_indication_test.go
├── rapid_resynchronization_request.go
├── rapid_resynchronization_request_test.go
├── raw_packet.go
├── raw_packet_test.go
├── receiver_estimated_maximum_bitrate.go
├── receiver_estimated_maximum_bitrate_test.go
├── receiver_report.go
├── receiver_report_test.go
├── reception_report.go
├── renovate.json
├── rfc8888.go
├── rfc8888_test.go
├── sender_report.go
├── sender_report_test.go
├── slice_loss_indication.go
├── slice_loss_indication_test.go
├── source_description.go
├── source_description_test.go
├── testdata/
│   └── fuzz/
│       └── FuzzUnmarshal/
│           ├── 0b954a73147600a3
│           ├── 16c369bd58290097
│           ├── 5eaf215c68e1ddb3
│           ├── 60753346a105d3c3
│           ├── 6366fbb9980fa33a
│           └── e1a48af9f8e7db71
├── transport_layer_cc.go
├── transport_layer_cc_test.go
├── transport_layer_nack.go
├── transport_layer_nack_test.go
├── util.go
└── util_test.go
Download .txt
SYMBOL INDEX (408 symbols across 44 files)

FILE: application_defined.go
  type ApplicationDefined (line 11) | type ApplicationDefined struct
    method DestinationSSRC (line 19) | func (a ApplicationDefined) DestinationSSRC() []uint32 {
    method Marshal (line 24) | func (a ApplicationDefined) Marshal() ([]byte, error) {
    method Unmarshal (line 68) | func (a *ApplicationDefined) Unmarshal(rawPacket []byte) error {
    method MarshalSize (line 114) | func (a *ApplicationDefined) MarshalSize() int {

FILE: application_defined_test.go
  function TestTApplicationPacketUnmarshal (line 12) | func TestTApplicationPacketUnmarshal(t *testing.T) {
  function TestTApplicationPacketMarshal (line 144) | func TestTApplicationPacketMarshal(t *testing.T) {

FILE: compound_packet.go
  type CompoundPacket (line 23) | type CompoundPacket
    method Validate (line 28) | func (c CompoundPacket) Validate() error {
    method CNAME (line 78) | func (c CompoundPacket) CNAME() (string, error) {
    method Marshal (line 107) | func (c CompoundPacket) Marshal() ([]byte, error) {
    method MarshalSize (line 118) | func (c CompoundPacket) MarshalSize() int {
    method Unmarshal (line 128) | func (c *CompoundPacket) Unmarshal(rawData []byte) error {
    method DestinationSSRC (line 146) | func (c CompoundPacket) DestinationSSRC() []uint32 {
    method String (line 154) | func (c CompoundPacket) String() string {

FILE: compound_packet_test.go
  function TestReadEOF (line 14) | func TestReadEOF(t *testing.T) {
  function TestBadCompound (line 23) | func TestBadCompound(t *testing.T) {
  function TestValidPacket (line 50) | func TestValidPacket(t *testing.T) {
  function TestCNAME (line 133) | func TestCNAME(t *testing.T) {
  function TestCompoundPacketRoundTrip (line 214) | func TestCompoundPacketRoundTrip(t *testing.T) {

FILE: extended_report.go
  type ExtendedReport (line 27) | type ExtendedReport struct
    method MarshalSize (line 564) | func (x ExtendedReport) MarshalSize() int {
    method Marshal (line 569) | func (x ExtendedReport) Marshal() ([]byte, error) {
    method Unmarshal (line 605) | func (x *ExtendedReport) Unmarshal(b []byte) error {
    method DestinationSSRC (line 665) | func (x *ExtendedReport) DestinationSSRC() []uint32 {
    method String (line 675) | func (x *ExtendedReport) String() string {
  type ReportBlock (line 34) | type ReportBlock interface
  type TypeSpecificField (line 44) | type TypeSpecificField
  type XRHeader (line 51) | type XRHeader struct
  type BlockTypeType (line 58) | type BlockTypeType
    method String (line 72) | func (t BlockTypeType) String() string {
  constant LossRLEReportBlockType (line 62) | LossRLEReportBlockType               = 1
  constant DuplicateRLEReportBlockType (line 63) | DuplicateRLEReportBlockType          = 2
  constant PacketReceiptTimesReportBlockType (line 64) | PacketReceiptTimesReportBlockType    = 3
  constant ReceiverReferenceTimeReportBlockType (line 65) | ReceiverReferenceTimeReportBlockType = 4
  constant DLRRReportBlockType (line 66) | DLRRReportBlockType                  = 5
  constant StatisticsSummaryReportBlockType (line 67) | StatisticsSummaryReportBlockType     = 6
  constant VoIPMetricsReportBlockType (line 68) | VoIPMetricsReportBlockType           = 7
  type rleReportBlock (line 114) | type rleReportBlock struct
  type Chunk (line 149) | type Chunk
    method String (line 199) | func (c Chunk) String() string {
    method Type (line 215) | func (c Chunk) Type() ChunkType {
    method RunType (line 225) | func (c Chunk) RunType() (uint, error) {
    method Value (line 234) | func (c Chunk) Value() uint {
  type LossRLEReportBlock (line 153) | type LossRLEReportBlock
    method DestinationSSRC (line 156) | func (b *LossRLEReportBlock) DestinationSSRC() []uint32 {
    method setupBlockHeader (line 160) | func (b *LossRLEReportBlock) setupBlockHeader() {
    method unpackBlockHeader (line 166) | func (b *LossRLEReportBlock) unpackBlockHeader() {
  type DuplicateRLEReportBlock (line 172) | type DuplicateRLEReportBlock
    method DestinationSSRC (line 175) | func (b *DuplicateRLEReportBlock) DestinationSSRC() []uint32 {
    method setupBlockHeader (line 179) | func (b *DuplicateRLEReportBlock) setupBlockHeader() {
    method unpackBlockHeader (line 185) | func (b *DuplicateRLEReportBlock) unpackBlockHeader() {
  type ChunkType (line 190) | type ChunkType
  constant RunLengthChunkType (line 194) | RunLengthChunkType       = 0
  constant BitVectorChunkType (line 195) | BitVectorChunkType       = 1
  constant TerminatingNullChunkType (line 196) | TerminatingNullChunkType = 2
  type PacketReceiptTimesReportBlock (line 269) | type PacketReceiptTimesReportBlock struct
    method DestinationSSRC (line 279) | func (b *PacketReceiptTimesReportBlock) DestinationSSRC() []uint32 {
    method setupBlockHeader (line 283) | func (b *PacketReceiptTimesReportBlock) setupBlockHeader() {
    method unpackBlockHeader (line 289) | func (b *PacketReceiptTimesReportBlock) unpackBlockHeader() {
  type ReceiverReferenceTimeReportBlock (line 307) | type ReceiverReferenceTimeReportBlock struct
    method DestinationSSRC (line 313) | func (b *ReceiverReferenceTimeReportBlock) DestinationSSRC() []uint32 {
    method setupBlockHeader (line 317) | func (b *ReceiverReferenceTimeReportBlock) setupBlockHeader() {
    method unpackBlockHeader (line 323) | func (b *ReceiverReferenceTimeReportBlock) unpackBlockHeader() {
  type DLRRReportBlock (line 346) | type DLRRReportBlock struct
    method DestinationSSRC (line 359) | func (b *DLRRReportBlock) DestinationSSRC() []uint32 {
    method setupBlockHeader (line 368) | func (b *DLRRReportBlock) setupBlockHeader() {
    method unpackBlockHeader (line 374) | func (b *DLRRReportBlock) unpackBlockHeader() {
  type DLRRReport (line 352) | type DLRRReport struct
  type StatisticsSummaryReportBlock (line 405) | type StatisticsSummaryReportBlock struct
    method DestinationSSRC (line 451) | func (b *StatisticsSummaryReportBlock) DestinationSSRC() []uint32 {
    method setupBlockHeader (line 455) | func (b *StatisticsSummaryReportBlock) setupBlockHeader() {
    method unpackBlockHeader (line 471) | func (b *StatisticsSummaryReportBlock) unpackBlockHeader() {
  type TTLorHopLimitType (line 428) | type TTLorHopLimitType
    method String (line 437) | func (t TTLorHopLimitType) String() string {
  constant ToHMissing (line 432) | ToHMissing = 0
  constant ToHIPv4 (line 433) | ToHIPv4    = 1
  constant ToHIPv6 (line 434) | ToHIPv6    = 2
  type VoIPMetricsReportBlock (line 504) | type VoIPMetricsReportBlock struct
    method DestinationSSRC (line 531) | func (b *VoIPMetricsReportBlock) DestinationSSRC() []uint32 {
    method setupBlockHeader (line 535) | func (b *VoIPMetricsReportBlock) setupBlockHeader() {
    method unpackBlockHeader (line 541) | func (b *VoIPMetricsReportBlock) unpackBlockHeader() {
  type UnknownReportBlock (line 546) | type UnknownReportBlock struct
    method DestinationSSRC (line 552) | func (b *UnknownReportBlock) DestinationSSRC() []uint32 {
    method setupBlockHeader (line 556) | func (b *UnknownReportBlock) setupBlockHeader() {
    method unpackBlockHeader (line 560) | func (b *UnknownReportBlock) unpackBlockHeader() {

FILE: extended_report_test.go
  function testPacket (line 28) | func testPacket() Packet {
  function encodedPacket (line 136) | func encodedPacket() []byte {
  function TestEncode (line 225) | func TestEncode(t *testing.T) {
  function TestDecode (line 237) | func TestDecode(t *testing.T) {

FILE: full_intra_request.go
  type FIREntry (line 13) | type FIREntry struct
  type FullIntraRequest (line 21) | type FullIntraRequest struct
    method Marshal (line 35) | func (p FullIntraRequest) Marshal() ([]byte, error) {
    method Unmarshal (line 53) | func (p *FullIntraRequest) Unmarshal(rawPacket []byte) error {
    method Header (line 89) | func (p *FullIntraRequest) Header() Header {
    method MarshalSize (line 98) | func (p *FullIntraRequest) MarshalSize() int {
    method String (line 102) | func (p *FullIntraRequest) String() string {
    method DestinationSSRC (line 114) | func (p *FullIntraRequest) DestinationSSRC() []uint32 {
  constant firOffset (line 29) | firOffset = 8

FILE: full_intra_request_test.go
  function TestFullIntraRequestUnmarshal (line 12) | func TestFullIntraRequestUnmarshal(t *testing.T) {
  function TestFullIntraRequestRoundTrip (line 153) | func TestFullIntraRequestRoundTrip(t *testing.T) {
  function TestFullIntraRequestUnmarshalHeader (line 194) | func TestFullIntraRequestUnmarshalHeader(t *testing.T) {

FILE: fuzz_test.go
  function FuzzUnmarshal (line 10) | func FuzzUnmarshal(f *testing.F) {

FILE: goodbye.go
  type Goodbye (line 13) | type Goodbye struct
    method Marshal (line 21) | func (g Goodbye) Marshal() ([]byte, error) {
    method Unmarshal (line 69) | func (g *Goodbye) Unmarshal(rawPacket []byte) error {
    method Header (line 125) | func (g *Goodbye) Header() Header {
    method MarshalSize (line 135) | func (g *Goodbye) MarshalSize() int {
    method DestinationSSRC (line 150) | func (g *Goodbye) DestinationSSRC() []uint32 {
    method String (line 157) | func (g Goodbye) String() string {

FILE: goodbye_test.go
  function TestGoodbyeUnmarshal (line 15) | func TestGoodbyeUnmarshal(t *testing.T) {
  function TestGoodbyeRoundTrip (line 126) | func TestGoodbyeRoundTrip(t *testing.T) {

FILE: header.go
  type PacketType (line 11) | type PacketType
    method String (line 43) | func (p PacketType) String() string {
  constant TypeSenderReport (line 17) | TypeSenderReport              PacketType = 200
  constant TypeReceiverReport (line 18) | TypeReceiverReport            PacketType = 201
  constant TypeSourceDescription (line 19) | TypeSourceDescription         PacketType = 202
  constant TypeGoodbye (line 20) | TypeGoodbye                   PacketType = 203
  constant TypeApplicationDefined (line 21) | TypeApplicationDefined        PacketType = 204
  constant TypeTransportSpecificFeedback (line 22) | TypeTransportSpecificFeedback PacketType = 205
  constant TypePayloadSpecificFeedback (line 23) | TypePayloadSpecificFeedback   PacketType = 206
  constant TypeExtendedReport (line 24) | TypeExtendedReport            PacketType = 207
  constant FormatSLI (line 31) | FormatSLI  uint8 = 2
  constant FormatPLI (line 32) | FormatPLI  uint8 = 1
  constant FormatFIR (line 33) | FormatFIR  uint8 = 4
  constant FormatTLN (line 34) | FormatTLN  uint8 = 1
  constant FormatRRR (line 35) | FormatRRR  uint8 = 5
  constant FormatCCFB (line 36) | FormatCCFB uint8 = 11
  constant FormatREMB (line 37) | FormatREMB uint8 = 15
  constant FormatTCC (line 40) | FormatTCC uint8 = 15
  constant rtpVersion (line 66) | rtpVersion = 2
  type Header (line 69) | type Header struct
    method Marshal (line 95) | func (h Header) Marshal() ([]byte, error) {
    method Unmarshal (line 124) | func (h *Header) Unmarshal(rawPacket []byte) error {
  constant headerLength (line 84) | headerLength = 4
  constant versionShift (line 85) | versionShift = 6
  constant versionMask (line 86) | versionMask  = 0x3
  constant paddingShift (line 87) | paddingShift = 5
  constant paddingMask (line 88) | paddingMask  = 0x1
  constant countShift (line 89) | countShift   = 0
  constant countMask (line 90) | countMask    = 0x1f
  constant countMax (line 91) | countMax     = (1 << 5) - 1

FILE: header_test.go
  function TestHeaderUnmarshal (line 12) | func TestHeaderUnmarshal(t *testing.T) {
  function TestHeaderRoundTrip (line 65) | func TestHeaderRoundTrip(t *testing.T) {

FILE: packet.go
  type Packet (line 8) | type Packet interface
  function Unmarshal (line 23) | func Unmarshal(rawData []byte) ([]Packet, error) {
  function Marshal (line 46) | func Marshal(packets []Packet) ([]byte, error) {
  function unmarshal (line 63) | func unmarshal(rawData []byte) (packet Packet, bytesprocessed int, err e...

FILE: packet_buffer.go
  type packetBuffer (line 39) | type packetBuffer struct
    method write (line 51) | func (b *packetBuffer) write(v any) error {
    method read (line 132) | func (b *packetBuffer) read(v any) error {
    method split (line 220) | func (b *packetBuffer) split(size int) packetBuffer {
  constant omit (line 43) | omit = "omit"
  function wireSize (line 235) | func wireSize(v any) int {

FILE: packet_buffer_test.go
  function TestWrite (line 12) | func TestWrite(t *testing.T) {
  function TestReadUint8 (line 64) | func TestReadUint8(t *testing.T) {
  function TestReadUint16 (line 74) | func TestReadUint16(t *testing.T) {
  function TestReadUint32 (line 84) | func TestReadUint32(t *testing.T) {
  function TestReadUint64 (line 94) | func TestReadUint64(t *testing.T) {
  function TestReadStruct (line 104) | func TestReadStruct(t *testing.T) {
  function TestReadSlice (line 120) | func TestReadSlice(t *testing.T) {
  function TestReadComplex (line 130) | func TestReadComplex(t *testing.T) {

FILE: packet_stringifier.go
  function stringify (line 33) | func stringify(p Packet) string {
  function formatField (line 40) | func formatField(name string, format string, f any, indent string) string {

FILE: packet_stringifier_test.go
  function TestPrint (line 13) | func TestPrint(t *testing.T) {

FILE: packet_test.go
  function realPacket (line 13) | func realPacket() []byte {
  function TestUnmarshal (line 85) | func TestUnmarshal(t *testing.T) {
  function TestUnmarshalNil (line 125) | func TestUnmarshalNil(t *testing.T) {
  function TestInvalidHeaderLength (line 130) | func TestInvalidHeaderLength(t *testing.T) {

FILE: picture_loss_indication.go
  type PictureLossIndication (line 13) | type PictureLossIndication struct
    method Marshal (line 26) | func (p PictureLossIndication) Marshal() ([]byte, error) {
    method Unmarshal (line 54) | func (p *PictureLossIndication) Unmarshal(rawPacket []byte) error {
    method Header (line 75) | func (p *PictureLossIndication) Header() Header {
    method MarshalSize (line 84) | func (p *PictureLossIndication) MarshalSize() int {
    method String (line 88) | func (p *PictureLossIndication) String() string {
    method DestinationSSRC (line 93) | func (p *PictureLossIndication) DestinationSSRC() []uint32 {
  constant pliLength (line 22) | pliLength = 2

FILE: picture_loss_indication_test.go
  function TestPictureLossIndicationUnmarshal (line 14) | func TestPictureLossIndicationUnmarshal(t *testing.T) {
  function TestPictureLossIndicationRoundTrip (line 88) | func TestPictureLossIndicationRoundTrip(t *testing.T) {
  function TestPictureLossIndicationUnmarshalHeader (line 121) | func TestPictureLossIndicationUnmarshalHeader(t *testing.T) {

FILE: rapid_resynchronization_request.go
  type RapidResynchronizationRequest (line 13) | type RapidResynchronizationRequest struct
    method Marshal (line 32) | func (p RapidResynchronizationRequest) Marshal() ([]byte, error) {
    method Unmarshal (line 55) | func (p *RapidResynchronizationRequest) Unmarshal(rawPacket []byte) er...
    method MarshalSize (line 76) | func (p *RapidResynchronizationRequest) MarshalSize() int {
    method Header (line 81) | func (p *RapidResynchronizationRequest) Header() Header {
    method DestinationSSRC (line 90) | func (p *RapidResynchronizationRequest) DestinationSSRC() []uint32 {
    method String (line 94) | func (p *RapidResynchronizationRequest) String() string {
  constant rrrLength (line 26) | rrrLength       = 2
  constant rrrHeaderLength (line 27) | rrrHeaderLength = ssrcLength * 2
  constant rrrMediaOffset (line 28) | rrrMediaOffset  = 4

FILE: rapid_resynchronization_request_test.go
  function TestRapidResynchronizationRequestUnmarshal (line 14) | func TestRapidResynchronizationRequestUnmarshal(t *testing.T) {
  function TestRapidResynchronizationRequestRoundTrip (line 84) | func TestRapidResynchronizationRequestRoundTrip(t *testing.T) {

FILE: raw_packet.go
  type RawPacket (line 10) | type RawPacket
    method Marshal (line 13) | func (r RawPacket) Marshal() ([]byte, error) {
    method Unmarshal (line 18) | func (r *RawPacket) Unmarshal(b []byte) error {
    method Header (line 30) | func (r RawPacket) Header() Header {
    method DestinationSSRC (line 40) | func (r *RawPacket) DestinationSSRC() []uint32 {
    method String (line 44) | func (r RawPacket) String() string {
    method MarshalSize (line 51) | func (r RawPacket) MarshalSize() int {

FILE: raw_packet_test.go
  function TestRawPacketRoundTrip (line 14) | func TestRawPacketRoundTrip(t *testing.T) {

FILE: receiver_estimated_maximum_bitrate.go
  type ReceiverEstimatedMaximumBitrate (line 15) | type ReceiverEstimatedMaximumBitrate struct
    method Marshal (line 27) | func (p ReceiverEstimatedMaximumBitrate) Marshal() (buf []byte, err er...
    method MarshalSize (line 46) | func (p ReceiverEstimatedMaximumBitrate) MarshalSize() int {
    method MarshalTo (line 51) | func (p ReceiverEstimatedMaximumBitrate) MarshalTo(buf []byte) (n int,...
    method Unmarshal (line 142) | func (p *ReceiverEstimatedMaximumBitrate) Unmarshal(buf []byte) (err e...
    method Header (line 260) | func (p *ReceiverEstimatedMaximumBitrate) Header() Header {
    method String (line 269) | func (p *ReceiverEstimatedMaximumBitrate) String() string {
    method DestinationSSRC (line 289) | func (p *ReceiverEstimatedMaximumBitrate) DestinationSSRC() []uint32 {

FILE: receiver_estimated_maximum_bitrate_test.go
  function TestReceiverEstimatedMaximumBitrateMarshal (line 15) | func TestReceiverEstimatedMaximumBitrateMarshal(t *testing.T) {
  function TestReceiverEstimatedMaximumBitrateUnmarshal (line 31) | func TestReceiverEstimatedMaximumBitrateUnmarshal(t *testing.T) {
  function TestReceiverEstimatedMaximumBitrateTruncate (line 52) | func TestReceiverEstimatedMaximumBitrateTruncate(t *testing.T) {
  function TestReceiverEstimatedMaximumBitrateOverflow (line 95) | func TestReceiverEstimatedMaximumBitrateOverflow(t *testing.T) {

FILE: receiver_report.go
  type ReceiverReport (line 13) | type ReceiverReport struct
    method Marshal (line 33) | func (r ReceiverReport) Marshal() ([]byte, error) {
    method Unmarshal (line 101) | func (r *ReceiverReport) Unmarshal(rawPacket []byte) error {
    method MarshalSize (line 163) | func (r *ReceiverReport) MarshalSize() int {
    method Header (line 173) | func (r *ReceiverReport) Header() Header {
    method DestinationSSRC (line 182) | func (r *ReceiverReport) DestinationSSRC() []uint32 {
    method String (line 191) | func (r ReceiverReport) String() string {
  constant ssrcLength (line 27) | ssrcLength     = 4
  constant rrSSRCOffset (line 28) | rrSSRCOffset   = headerLength
  constant rrReportOffset (line 29) | rrReportOffset = rrSSRCOffset + ssrcLength

FILE: receiver_report_test.go
  function TestReceiverReportUnmarshal (line 14) | func TestReceiverReportUnmarshal(t *testing.T) {
  function tooManyReports (line 169) | func tooManyReports() []ReceptionReport {
  function TestReceiverReportRoundTrip (line 187) | func TestReceiverReportRoundTrip(t *testing.T) {

FILE: reception_report.go
  type ReceptionReport (line 10) | type ReceptionReport struct
    method Marshal (line 51) | func (r ReceptionReport) Marshal() ([]byte, error) {
    method Unmarshal (line 94) | func (r *ReceptionReport) Unmarshal(rawPacket []byte) error {
    method len (line 131) | func (r *ReceptionReport) len() int {
  constant receptionReportLength (line 41) | receptionReportLength = 24
  constant fractionLostOffset (line 42) | fractionLostOffset    = 4
  constant totalLostOffset (line 43) | totalLostOffset       = 5
  constant lastSeqOffset (line 44) | lastSeqOffset         = 8
  constant jitterOffset (line 45) | jitterOffset          = 12
  constant lastSROffset (line 46) | lastSROffset          = 16
  constant delayOffset (line 47) | delayOffset           = 20

FILE: rfc8888.go
  type ECN (line 50) | type ECN
    method String (line 69) | func (e ECN) String() string {
  constant ECNNonECT (line 55) | ECNNonECT ECN = iota
  constant ECNECT1 (line 59) | ECNECT1
  constant ECNECT0 (line 63) | ECNECT0
  constant ECNCE (line 66) | ECNCE
  constant reportTimestampLength (line 89) | reportTimestampLength = 4
  constant reportBlockOffset (line 90) | reportBlockOffset     = 8
  type CCFeedbackReport (line 95) | type CCFeedbackReport struct
    method DestinationSSRC (line 107) | func (b CCFeedbackReport) DestinationSSRC() []uint32 {
    method Len (line 117) | func (b *CCFeedbackReport) Len() int {
    method MarshalSize (line 122) | func (b *CCFeedbackReport) MarshalSize() int {
    method Header (line 132) | func (b *CCFeedbackReport) Header() Header {
    method Marshal (line 142) | func (b CCFeedbackReport) Marshal() ([]byte, error) {
    method String (line 167) | func (b CCFeedbackReport) String() string {
    method Unmarshal (line 182) | func (b *CCFeedbackReport) Unmarshal(rawPacket []byte) error {
  constant ssrcOffset (line 215) | ssrcOffset          = 0
  constant beginSequenceOffset (line 216) | beginSequenceOffset = 4
  constant numReportsOffset (line 217) | numReportsOffset    = 6
  constant reportsOffset (line 218) | reportsOffset       = 8
  constant maxMetricBlocks (line 220) | maxMetricBlocks = 16384
  type CCFeedbackReportBlock (line 224) | type CCFeedbackReportBlock struct
    method len (line 232) | func (b *CCFeedbackReportBlock) len() int {
    method String (line 241) | func (b CCFeedbackReportBlock) String() string {
    method marshal (line 260) | func (b CCFeedbackReportBlock) marshal() ([]byte, error) {
    method unmarshal (line 285) | func (b *CCFeedbackReportBlock) unmarshal(rawPacket []byte) error {
  constant metricBlockLength (line 318) | metricBlockLength = 2
  type CCFeedbackMetricBlock (line 322) | type CCFeedbackMetricBlock struct
    method marshal (line 331) | func (b CCFeedbackMetricBlock) marshal() ([]byte, error) {
    method unmarshal (line 356) | func (b *CCFeedbackMetricBlock) unmarshal(rawPacket []byte) error {

FILE: rfc8888_test.go
  function TestCCFeedbackMetricBlockUnmarshalMarshal (line 16) | func TestCCFeedbackMetricBlockUnmarshalMarshal(t *testing.T) {
  function TestCCFeedbackReportBlockUnmarshalMarshal (line 133) | func TestCCFeedbackReportBlockUnmarshalMarshal(t *testing.T) {
  function TestCCFeedbackReportUnmarshalMarshal (line 315) | func TestCCFeedbackReportUnmarshalMarshal(t *testing.T) {
  function TestCCFeedbackOverflow (line 428) | func TestCCFeedbackOverflow(t *testing.T) {

FILE: sender_report.go
  type SenderReport (line 13) | type SenderReport struct
    method Marshal (line 61) | func (r SenderReport) Marshal() ([]byte, error) {
    method Unmarshal (line 135) | func (r *SenderReport) Unmarshal(rawPacket []byte) error {
    method DestinationSSRC (line 223) | func (r *SenderReport) DestinationSSRC() []uint32 {
    method MarshalSize (line 234) | func (r *SenderReport) MarshalSize() int {
    method Header (line 244) | func (r *SenderReport) Header() Header {
    method String (line 252) | func (r SenderReport) String() string {
  constant srHeaderLength (line 47) | srHeaderLength      = 24
  constant srSSRCOffset (line 48) | srSSRCOffset        = 0
  constant srNTPOffset (line 49) | srNTPOffset         = srSSRCOffset + ssrcLength
  constant ntpTimeLength (line 50) | ntpTimeLength       = 8
  constant srRTPOffset (line 51) | srRTPOffset         = srNTPOffset + ntpTimeLength
  constant rtpTimeLength (line 52) | rtpTimeLength       = 4
  constant srPacketCountOffset (line 53) | srPacketCountOffset = srRTPOffset + rtpTimeLength
  constant srPacketCountLength (line 54) | srPacketCountLength = 4
  constant srOctetCountOffset (line 55) | srOctetCountOffset  = srPacketCountOffset + srPacketCountLength
  constant srOctetCountLength (line 56) | srOctetCountLength  = 4
  constant srReportOffset (line 57) | srReportOffset      = srOctetCountOffset + srOctetCountLength

FILE: sender_report_test.go
  function TestSenderReportUnmarshal (line 15) | func TestSenderReportUnmarshal(t *testing.T) {
  function TestSenderReportRoundTrip (line 197) | func TestSenderReportRoundTrip(t *testing.T) {

FILE: slice_loss_indication.go
  type SLIEntry (line 14) | type SLIEntry struct
  type SliceLossIndication (line 26) | type SliceLossIndication struct
    method Marshal (line 42) | func (p SliceLossIndication) Marshal() ([]byte, error) {
    method Unmarshal (line 65) | func (p *SliceLossIndication) Unmarshal(rawPacket []byte) error {
    method MarshalSize (line 98) | func (p *SliceLossIndication) MarshalSize() int {
    method Header (line 103) | func (p *SliceLossIndication) Header() Header {
    method String (line 111) | func (p *SliceLossIndication) String() string {
    method DestinationSSRC (line 116) | func (p *SliceLossIndication) DestinationSSRC() []uint32 {
  constant sliLength (line 37) | sliLength = 2
  constant sliOffset (line 38) | sliOffset = 8

FILE: slice_loss_indication_test.go
  function TestSliceLossIndicationUnmarshal (line 14) | func TestSliceLossIndicationUnmarshal(t *testing.T) {
  function TestSliceLossIndicationRoundTrip (line 88) | func TestSliceLossIndicationRoundTrip(t *testing.T) {

FILE: source_description.go
  type SDESType (line 13) | type SDESType
    method String (line 31) | func (s SDESType) String() string {
  constant SDESEnd (line 19) | SDESEnd      SDESType = iota
  constant SDESCNAME (line 20) | SDESCNAME
  constant SDESName (line 21) | SDESName
  constant SDESEmail (line 22) | SDESEmail
  constant SDESPhone (line 23) | SDESPhone
  constant SDESLocation (line 24) | SDESLocation
  constant SDESTool (line 25) | SDESTool
  constant SDESNote (line 26) | SDESNote
  constant SDESPrivate (line 27) | SDESPrivate
  constant sdesSourceLen (line 57) | sdesSourceLen        = 4
  constant sdesTypeLen (line 58) | sdesTypeLen          = 1
  constant sdesTypeOffset (line 59) | sdesTypeOffset       = 0
  constant sdesOctetCountLen (line 60) | sdesOctetCountLen    = 1
  constant sdesOctetCountOffset (line 61) | sdesOctetCountOffset = 1
  constant sdesMaxOctetCount (line 62) | sdesMaxOctetCount    = (1 << 8) - 1
  constant sdesTextOffset (line 63) | sdesTextOffset       = 2
  type SourceDescription (line 67) | type SourceDescription struct
    method Marshal (line 85) | func (s SourceDescription) Marshal() ([]byte, error) {
    method Unmarshal (line 131) | func (s *SourceDescription) Unmarshal(rawPacket []byte) error {
    method MarshalSize (line 177) | func (s *SourceDescription) MarshalSize() int {
    method Header (line 187) | func (s *SourceDescription) Header() Header {
    method DestinationSSRC (line 359) | func (s *SourceDescription) DestinationSSRC() []uint32 {
    method String (line 368) | func (s *SourceDescription) String() string {
  function NewCNAMESourceDescription (line 72) | func NewCNAMESourceDescription(ssrc uint32, cname string) *SourceDescrip...
  type SourceDescriptionChunk (line 196) | type SourceDescriptionChunk struct
    method Marshal (line 203) | func (s SourceDescriptionChunk) Marshal() ([]byte, error) {
    method Unmarshal (line 234) | func (s *SourceDescriptionChunk) Unmarshal(rawPacket []byte) error {
    method len (line 266) | func (s SourceDescriptionChunk) len() int {
  type SourceDescriptionItem (line 280) | type SourceDescriptionItem struct
    method Len (line 290) | func (s SourceDescriptionItem) Len() int {
    method Marshal (line 302) | func (s SourceDescriptionItem) Marshal() ([]byte, error) {
    method Unmarshal (line 332) | func (s *SourceDescriptionItem) Unmarshal(rawPacket []byte) error {

FILE: source_description_test.go
  function TestSourceDescriptionUnmarshal (line 15) | func TestSourceDescriptionUnmarshal(t *testing.T) {
  function TestSourceDescriptionRoundTrip (line 230) | func TestSourceDescriptionRoundTrip(t *testing.T) {

FILE: transport_layer_cc.go
  constant TypeTCCRunLengthChunk (line 46) | TypeTCCRunLengthChunk    = 0
  constant TypeTCCStatusVectorChunk (line 47) | TypeTCCStatusVectorChunk = 1
  constant packetStatusChunkLength (line 50) | packetStatusChunkLength = 2
  constant TypeTCCPacketNotReceived (line 56) | TypeTCCPacketNotReceived = uint16(iota)
  constant TypeTCCPacketReceivedSmallDelta (line 57) | TypeTCCPacketReceivedSmallDelta
  constant TypeTCCPacketReceivedLargeDelta (line 58) | TypeTCCPacketReceivedLargeDelta
  constant TypeTCCPacketReceivedWithoutDelta (line 61) | TypeTCCPacketReceivedWithoutDelta
  constant TypeTCCSymbolSizeOneBit (line 67) | TypeTCCSymbolSizeOneBit = 0
  constant TypeTCCSymbolSizeTwoBit (line 68) | TypeTCCSymbolSizeTwoBit = 1
  function numOfBitsOfSymbolSize (line 76) | func numOfBitsOfSymbolSize() map[uint16]uint16 {
  type PacketStatusChunk (line 90) | type PacketStatusChunk interface
  type RunLengthChunk (line 102) | type RunLengthChunk struct
    method Marshal (line 117) | func (r RunLengthChunk) Marshal() ([]byte, error) {
    method Unmarshal (line 144) | func (r *RunLengthChunk) Unmarshal(rawPacket []byte) error {
  type StatusVectorChunk (line 170) | type StatusVectorChunk struct
    method Marshal (line 186) | func (r StatusVectorChunk) Marshal() ([]byte, error) {
    method Unmarshal (line 218) | func (r *StatusVectorChunk) Unmarshal(rawPacket []byte) error {
  constant TypeTCCDeltaScaleFactor (line 254) | TypeTCCDeltaScaleFactor = 250
  type RecvDelta (line 261) | type RecvDelta struct
    method Marshal (line 268) | func (r RecvDelta) Marshal() ([]byte, error) {
    method Unmarshal (line 292) | func (r *RecvDelta) Unmarshal(rawPacket []byte) error {
  constant baseSequenceNumberOffset (line 315) | baseSequenceNumberOffset = 8
  constant packetStatusCountOffset (line 316) | packetStatusCountOffset  = 10
  constant referenceTimeOffset (line 317) | referenceTimeOffset      = 12
  constant fbPktCountOffset (line 318) | fbPktCountOffset         = 15
  constant packetChunkOffset (line 319) | packetChunkOffset        = 16
  type TransportLayerCC (line 324) | type TransportLayerCC struct
    method packetLen (line 365) | func (t *TransportLayerCC) packetLen() uint16 {
    method Len (line 380) | func (t *TransportLayerCC) Len() uint16 {
    method MarshalSize (line 385) | func (t *TransportLayerCC) MarshalSize() int {
    method String (line 395) | func (t TransportLayerCC) String() string {
    method Marshal (line 418) | func (t TransportLayerCC) Marshal() ([]byte, error) {
    method Unmarshal (line 464) | func (t *TransportLayerCC) Unmarshal(rawPacket []byte) error {
    method DestinationSSRC (line 577) | func (t TransportLayerCC) DestinationSSRC() []uint32 {
  function localMin (line 581) | func localMin(x, y uint16) uint16 {

FILE: transport_layer_cc_test.go
  function TestTransportLayerCC_RunLengthChunkUnmarshal (line 14) | func TestTransportLayerCC_RunLengthChunkUnmarshal(t *testing.T) {
  function TestTransportLayerCC_RunLengthChunkMarshal (line 50) | func TestTransportLayerCC_RunLengthChunkMarshal(t *testing.T) {
  function TestTransportLayerCC_StatusVectorChunkUnmarshal (line 86) | func TestTransportLayerCC_StatusVectorChunkUnmarshal(t *testing.T) {
  function TestTransportLayerCC_StatusVectorChunkMarshal (line 147) | func TestTransportLayerCC_StatusVectorChunkMarshal(t *testing.T) {
  function TestTransportLayerCC_RecvDeltaUnmarshal (line 206) | func TestTransportLayerCC_RecvDeltaUnmarshal(t *testing.T) {
  function TestTransportLayerCC_RecvDeltaMarshal (line 250) | func TestTransportLayerCC_RecvDeltaMarshal(t *testing.T) {
  function TestTransportLayerCC_Unmarshal (line 318) | func TestTransportLayerCC_Unmarshal(t *testing.T) {
  function TestTransportLayerCC_Marshal (line 769) | func TestTransportLayerCC_Marshal(t *testing.T) {

FILE: transport_layer_nack.go
  type PacketBitmap (line 15) | type PacketBitmap
  type NackPair (line 19) | type NackPair struct
    method Range (line 67) | func (n *NackPair) Range(f func(seqno uint16) bool) {
    method PacketList (line 86) | func (n *NackPair) PacketList() []uint16 {
  type TransportLayerNack (line 30) | type TransportLayerNack struct
    method Marshal (line 103) | func (p TransportLayerNack) Marshal() ([]byte, error) {
    method Unmarshal (line 125) | func (p *TransportLayerNack) Unmarshal(rawPacket []byte) error {
    method MarshalSize (line 161) | func (p *TransportLayerNack) MarshalSize() int {
    method Header (line 166) | func (p *TransportLayerNack) Header() Header {
    method String (line 174) | func (p TransportLayerNack) String() string {
    method DestinationSSRC (line 187) | func (p *TransportLayerNack) DestinationSSRC() []uint32 {
  function NackPairsFromSequenceNumbers (line 42) | func NackPairsFromSequenceNumbers(sequenceNumbers []uint16) (pairs []Nac...
  constant tlnLength (line 98) | tlnLength  = 2
  constant nackOffset (line 99) | nackOffset = 8

FILE: transport_layer_nack_test.go
  function TestTransportLayerNackUnmarshal (line 14) | func TestTransportLayerNackUnmarshal(t *testing.T) {
  function TestTransportLayerNackRoundTrip (line 96) | func TestTransportLayerNackRoundTrip(t *testing.T) {
  function testNackPair (line 120) | func testNackPair(t *testing.T, s []uint16, n NackPair) {
  function TestNackPair (line 127) | func TestNackPair(t *testing.T) {
  function TestNackPairRange (line 135) | func TestNackPairRange(t *testing.T) {
  function TestTransportLayerNackPairGeneration (line 155) | func TestTransportLayerNackPairGeneration(t *testing.T) {

FILE: util.go
  function getPadding (line 7) | func getPadding(packetLen int) int {
  function setNBitsOfUint16 (line 16) | func setNBitsOfUint16(src, size, startIndex, val uint16) (uint16, error) {
  function appendNBitsToUint32 (line 28) | func appendNBitsToUint32(src, n, val uint32) uint32 {
  function getNBitsFromByte (line 33) | func getNBitsFromByte(b byte, begin, n uint16) uint16 {
  function get24BitsFromBytes (line 41) | func get24BitsFromBytes(b []byte) uint32 {

FILE: util_test.go
  function TestGetPadding (line 12) | func TestGetPadding(t *testing.T) {
  function TestSetNBitsOfUint16 (line 35) | func TestSetNBitsOfUint16(t *testing.T) {
Condensed preview — 76 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (348K chars).
[
  {
    "path": ".github/.gitignore",
    "chars": 110,
    "preview": "# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\n.goassets\n"
  },
  {
    "path": ".github/fetch-scripts.sh",
    "chars": 894,
    "preview": "#!/bin/sh\n\n#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n#"
  },
  {
    "path": ".github/install-hooks.sh",
    "chars": 594,
    "preview": "#!/bin/sh\n\n#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n#"
  },
  {
    "path": ".github/workflows/api.yaml",
    "chars": 586,
    "preview": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this re"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "chars": 716,
    "preview": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this re"
  },
  {
    "path": ".github/workflows/fuzz.yaml",
    "chars": 734,
    "preview": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this re"
  },
  {
    "path": ".github/workflows/lint.yaml",
    "chars": 634,
    "preview": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this re"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 678,
    "preview": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this re"
  },
  {
    "path": ".github/workflows/renovate-go-sum-fix.yaml",
    "chars": 693,
    "preview": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this re"
  },
  {
    "path": ".github/workflows/reuse.yml",
    "chars": 615,
    "preview": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this re"
  },
  {
    "path": ".github/workflows/test.yaml",
    "chars": 1739,
    "preview": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this re"
  },
  {
    "path": ".github/workflows/tidy-check.yaml",
    "chars": 702,
    "preview": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n# If this re"
  },
  {
    "path": ".gitignore",
    "chars": 410,
    "preview": "# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\n### JetBrains IDE ##"
  },
  {
    "path": ".golangci.yml",
    "chars": 8499,
    "preview": "# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nversion: \"2\"\nlinters"
  },
  {
    "path": ".goreleaser.yml",
    "chars": 121,
    "preview": "# SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n# SPDX-License-Identifier: MIT\n\nbuilds:\n- skip: true"
  },
  {
    "path": ".reuse/dep5",
    "chars": 588,
    "preview": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: Pion\nSource: https://github.co"
  },
  {
    "path": "LICENSE",
    "chars": 1093,
    "preview": "MIT License\n\nCopyright (c) 2026 The Pion community <https://pion.ly>\n\nPermission is hereby granted, free of charge, to a"
  },
  {
    "path": "LICENSES/CC0-1.0.txt",
    "chars": 7048,
    "preview": "Creative Commons Legal Code\n\nCC0 1.0 Universal\n\n    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE\n"
  },
  {
    "path": "LICENSES/MIT.txt",
    "chars": 1078,
    "preview": "MIT License\n\nCopyright (c) <year> <copyright holders>\n\nPermission is hereby granted, free of charge, to any person obtai"
  },
  {
    "path": "README.md",
    "chars": 2441,
    "preview": "<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"
  },
  {
    "path": "application_defined.go",
    "chars": 3457,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "application_defined_test.go",
    "chars": 6148,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "codecov.yml",
    "chars": 461,
    "preview": "#\n# DO NOT EDIT THIS FILE\n#\n# It is automatically copied from https://github.com/pion/.goassets repository.\n#\n# SPDX-Fil"
  },
  {
    "path": "compound_packet.go",
    "chars": 3854,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "compound_packet_test.go",
    "chars": 4605,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "doc.go",
    "chars": 1386,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\n/*\nPackage rtcp im"
  },
  {
    "path": "errors.go",
    "chars": 2662,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "extended_report.go",
    "chars": 22880,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "extended_report_test.go",
    "chars": 6605,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "full_intra_request.go",
    "chars": 3112,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "full_intra_request_test.go",
    "chars": 4820,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "fuzz_test.go",
    "chars": 410,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "go.mod",
    "chars": 231,
    "preview": "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-sp"
  },
  {
    "path": "go.sum",
    "chars": 883,
    "preview": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.m"
  },
  {
    "path": "goodbye.go",
    "chars": 4890,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "goodbye_test.go",
    "chars": 3998,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "header.go",
    "chars": 4306,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "header_test.go",
    "chars": 2039,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "packet.go",
    "chars": 3143,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\n// P"
  },
  {
    "path": "packet_buffer.go",
    "chars": 7191,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "packet_buffer_test.go",
    "chars": 3749,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "packet_stringifier.go",
    "chars": 3520,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "packet_stringifier_test.go",
    "chars": 13413,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "packet_test.go",
    "chars": 3128,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "picture_loss_indication.go",
    "chars": 2431,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "picture_loss_indication_test.go",
    "chars": 3231,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "rapid_resynchronization_request.go",
    "chars": 2759,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "rapid_resynchronization_request_test.go",
    "chars": 2430,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "raw_packet.go",
    "chars": 1147,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "raw_packet_test.go",
    "chars": 1364,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "receiver_estimated_maximum_bitrate.go",
    "chars": 8809,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "receiver_estimated_maximum_bitrate_test.go",
    "chars": 3544,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "receiver_report.go",
    "chars": 7761,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "receiver_report_test.go",
    "chars": 5821,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "reception_report.go",
    "chars": 5805,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "renovate.json",
    "chars": 123,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"github>pion/renovate-config\"\n  ]"
  },
  {
    "path": "rfc8888.go",
    "chars": 10326,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "rfc8888_test.go",
    "chars": 10729,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "sender_report.go",
    "chars": 11395,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "sender_report_test.go",
    "chars": 6396,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "slice_loss_indication.go",
    "chars": 3102,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "slice_loss_indication_test.go",
    "chars": 2512,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "source_description.go",
    "chars": 12231,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "source_description_test.go",
    "chars": 6930,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/0b954a73147600a3",
    "chars": 192,
    "preview": "go test fuzz v1\n[]byte(\"\\x8f\\xcd\\x00 0000000000000000A000000000000000000000000000000000000000000000000000000000000000000"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/16c369bd58290097",
    "chars": 232,
    "preview": "go test fuzz v1\n[]byte(\"\\x8f\\xcd\\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/5eaf215c68e1ddb3",
    "chars": 47,
    "preview": "go test fuzz v1\n[]byte(\"\\x81\\xcd\\x00\\x010000\")\n"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/60753346a105d3c3",
    "chars": 130,
    "preview": "go test fuzz v1\n[]byte(\"\\x8b\\xcd\\x00\\x150000000000\\x80 00000000000000000000000000000000000000000000000000000000000000000"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/6366fbb9980fa33a",
    "chars": 47,
    "preview": "go test fuzz v1\n[]byte(\"\\xa4\\xce\\x00\\x010000\")\n"
  },
  {
    "path": "testdata/fuzz/FuzzUnmarshal/e1a48af9f8e7db71",
    "chars": 72,
    "preview": "go test fuzz v1\n[]byte(\"\\xaf\\xcd\\x00\\a0000000000000000\\xf70A000000000\")\n"
  },
  {
    "path": "transport_layer_cc.go",
    "chars": 17270,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\n// A"
  },
  {
    "path": "transport_layer_cc_test.go",
    "chars": 29392,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "transport_layer_nack.go",
    "chars": 4859,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "transport_layer_nack_test.go",
    "chars": 4361,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  },
  {
    "path": "util.go",
    "chars": 1209,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\n// g"
  },
  {
    "path": "util_test.go",
    "chars": 1631,
    "preview": "// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>\n// SPDX-License-Identifier: MIT\n\npackage rtcp\n\nimpo"
  }
]

About this extraction

This page contains the full source code of the pion/rtcp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 76 files (301.9 KB), approximately 101.3k tokens, and a symbol index with 408 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!