pax_global_header00006660000000000000000000000064151215440660014516gustar00rootroot0000000000000052 comment=15773549265b52cc4224a2f19c795797303cf582 rtp-1.8.27/000077500000000000000000000000001512154406600124225ustar00rootroot00000000000000rtp-1.8.27/.github/000077500000000000000000000000001512154406600137625ustar00rootroot00000000000000rtp-1.8.27/.github/.gitignore000066400000000000000000000001561512154406600157540ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT .goassets rtp-1.8.27/.github/fetch-scripts.sh000077500000000000000000000016001512154406600170740ustar00rootroot00000000000000#!/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: 2023 The Pion community # SPDX-License-Identifier: MIT set -eu SCRIPT_PATH="$(realpath "$(dirname "$0")")" GOASSETS_PATH="${SCRIPT_PATH}/.goassets" GOASSETS_REF=${GOASSETS_REF:-master} 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 rtp-1.8.27/.github/install-hooks.sh000077500000000000000000000012421512154406600171070ustar00rootroot00000000000000#!/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: 2023 The Pion community # 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" cp "${GOASSETS_PATH}/hooks/pre-push.sh" "${SCRIPT_PATH}/../.git/hooks/pre-push" rtp-1.8.27/.github/workflows/000077500000000000000000000000001512154406600160175ustar00rootroot00000000000000rtp-1.8.27/.github/workflows/api.yaml000066400000000000000000000011141512154406600174510ustar00rootroot00000000000000# # 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: 2023 The Pion community # SPDX-License-Identifier: MIT name: API on: pull_request: jobs: check: uses: pion/.goassets/.github/workflows/api.reusable.yml@master rtp-1.8.27/.github/workflows/codeql-analysis.yml000066400000000000000000000013201512154406600216260ustar00rootroot00000000000000# # 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: 2023 The Pion community # SPDX-License-Identifier: MIT name: CodeQL on: workflow_dispatch: schedule: - cron: '23 5 * * 0' pull_request: branches: - master paths: - '**.go' jobs: analyze: uses: pion/.goassets/.github/workflows/codeql-analysis.reusable.yml@master rtp-1.8.27/.github/workflows/fuzz.yaml000066400000000000000000000013421512154406600177010ustar00rootroot00000000000000# # 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: 2023 The Pion community # SPDX-License-Identifier: MIT name: Fuzz on: push: branches: - master schedule: - cron: "0 */8 * * *" jobs: fuzz: uses: pion/.goassets/.github/workflows/fuzz.reusable.yml@master with: go-version: "1.25" # auto-update/latest-go-version fuzz-time: "60s" rtp-1.8.27/.github/workflows/lint.yaml000066400000000000000000000011151512154406600176470ustar00rootroot00000000000000# # 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: 2023 The Pion community # SPDX-License-Identifier: MIT name: Lint on: pull_request: jobs: lint: uses: pion/.goassets/.github/workflows/lint.reusable.yml@master rtp-1.8.27/.github/workflows/release.yml000066400000000000000000000012501512154406600201600ustar00rootroot00000000000000# # 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: 2023 The Pion community # SPDX-License-Identifier: MIT name: Release on: push: tags: - 'v*' jobs: release: uses: pion/.goassets/.github/workflows/release.reusable.yml@master with: go-version: "1.25" # auto-update/latest-go-version rtp-1.8.27/.github/workflows/renovate-go-sum-fix.yaml000066400000000000000000000012671512154406600225250ustar00rootroot00000000000000# # 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: 2023 The Pion community # 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@master secrets: token: ${{ secrets.PIONBOT_PRIVATE_KEY }} rtp-1.8.27/.github/workflows/reuse.yml000066400000000000000000000011511512154406600176630ustar00rootroot00000000000000# # 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: 2023 The Pion community # SPDX-License-Identifier: MIT name: REUSE Compliance Check on: push: pull_request: jobs: lint: uses: pion/.goassets/.github/workflows/reuse.reusable.yml@master rtp-1.8.27/.github/workflows/test.yaml000066400000000000000000000033271512154406600176670ustar00rootroot00000000000000# # 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: 2023 The Pion community # SPDX-License-Identifier: MIT name: Test on: push: branches: - master pull_request: jobs: test: uses: pion/.goassets/.github/workflows/test.reusable.yml@master 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@master 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@master 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@master 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@master with: go-version: "1.25" # auto-update/latest-go-version secrets: inherit rtp-1.8.27/.github/workflows/tidy-check.yaml000066400000000000000000000013021512154406600207230ustar00rootroot00000000000000# # 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: 2023 The Pion community # SPDX-License-Identifier: MIT name: Go mod tidy on: pull_request: push: branches: - master jobs: tidy: uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master with: go-version: "1.25" # auto-update/latest-go-version rtp-1.8.27/.gitignore000066400000000000000000000006321512154406600144130ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2023 The Pion community # 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 rtp-1.8.27/.golangci.yml000066400000000000000000000202661512154406600150140ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2023 The Pion community # 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 - 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 rtp-1.8.27/.goreleaser.yml000066400000000000000000000001711512154406600153520ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT builds: - skip: true rtp-1.8.27/.reuse/000077500000000000000000000000001512154406600136235ustar00rootroot00000000000000rtp-1.8.27/.reuse/dep5000066400000000000000000000011141512154406600144000ustar00rootroot00000000000000Format: 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: 2023 The Pion community License: MIT Files: testdata/seed/* testdata/fuzz/* **/testdata/fuzz/* api/*.txt Copyright: 2023 The Pion community License: CC0-1.0 rtp-1.8.27/LICENSE000066400000000000000000000020661512154406600134330ustar00rootroot00000000000000MIT License Copyright (c) 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. rtp-1.8.27/LICENSES/000077500000000000000000000000001512154406600136275ustar00rootroot00000000000000rtp-1.8.27/LICENSES/MIT.txt000066400000000000000000000020661512154406600150250ustar00rootroot00000000000000MIT License Copyright (c) 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. rtp-1.8.27/README.md000066400000000000000000000104211512154406600136770ustar00rootroot00000000000000


Pion RTP

A Go implementation of RTP

Pion RTP Sourcegraph Widget join us on Discord Follow us on Bluesky
GitHub Workflow Status Go Reference Coverage Status Go Report Card License: MIT


### Implemented - [RFC 3550](https://www.rfc-editor.org/rfc/rfc3550.html) — RTP: A Transport Protocol for Real-Time Applications - [RFC 8285](https://www.rfc-editor.org/rfc/rfc8285.html) — A General Mechanism for RTP Header Extensions #### Header Extensions - [RFC 6464](https://www.rfc-editor.org/rfc/rfc6464.html) — RTP Header Extension for Client-to-Mixer Audio Level Indication - [draft-holmer-rmcat-transport-wide-cc-extensions-01](https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-01) — Transport-Wide Congestion Control - [Absolute Send Time](https://webrtc.googlesource.com/src/%2B/refs/heads/main/docs/native-code/rtp-hdrext/abs-send-time/README.md) (WebRTC extension, non-RFC) - [Absolute Capture Time](https://webrtc.googlesource.com/src/%2B/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time/README.md) (WebRTC extension, non-RFC) - [Playout Delay](https://webrtc.googlesource.com/src/%2B/main/docs/native-code/rtp-hdrext/playout-delay/README.md) (WebRTC extension, non-RFC) - [Video Layers Allocation](https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-layers-allocation00) (WebRTC extension, non-RFC) #### Codecs - [RFC 3551](https://www.rfc-editor.org/rfc/rfc3551.html) — RTP Profile for PCMA/PCMU (G.711) and G.722 Audio - [RFC 6184](https://www.rfc-editor.org/rfc/rfc6184.html) — RTP Payload Format for H.264 Video - [RFC 7587](https://www.rfc-editor.org/rfc/rfc7587.html) — RTP Payload Format for the Opus Audio Codec - [RFC 7741](https://www.rfc-editor.org/rfc/rfc7741.html) — RTP Payload Format for VP8 Video - [draft-ietf-payload-vp9](https://datatracker.ietf.org/doc/draft-ietf-payload-vp9/) — RTP Payload Format for VP9 Video - [draft-ietf-avtcore-rtp-hevc](https://datatracker.ietf.org/doc/draft-ietf-avtcore-rtp-hevc/) — RTP Payload Format for H.265 Video - [AV1 RTP Payload Specification](https://aomediacodec.github.io/av1-rtp-spec/v1.0.0.html) — RTP Payload Format for AV1 Video ### 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 rtp-1.8.27/abscapturetimeextension.go000066400000000000000000000107601512154406600177220ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "encoding/binary" "io" "time" ) const ( absCaptureTimeExtensionSize = 8 absCaptureTimeExtendedExtensionSize = 16 ) // AbsCaptureTimeExtension is a extension payload format in. // http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | len=7 | absolute capture timestamp (bit 0-23) | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | absolute capture timestamp (bit 24-55) | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ... (56-63) | // +-+-+-+-+-+-+-+-+ // . type AbsCaptureTimeExtension struct { Timestamp uint64 EstimatedCaptureClockOffset *int64 } // MarshalSize returns the size of the AbsCaptureTimeExtension once marshaled. func (t AbsCaptureTimeExtension) MarshalSize() int { if t.EstimatedCaptureClockOffset != nil { return absCaptureTimeExtendedExtensionSize } return absCaptureTimeExtensionSize } // MarshalTo marshals the extension to the given buffer. // Returns io.ErrShortBuffer if buf is too small. func (t AbsCaptureTimeExtension) MarshalTo(buf []byte) (int, error) { if t.EstimatedCaptureClockOffset != nil { if len(buf) < absCaptureTimeExtendedExtensionSize { return 0, io.ErrShortBuffer } binary.BigEndian.PutUint64(buf[0:8], t.Timestamp) binary.BigEndian.PutUint64(buf[8:16], uint64(*t.EstimatedCaptureClockOffset)) // nolint: gosec // G115 return absCaptureTimeExtendedExtensionSize, nil } if len(buf) < absCaptureTimeExtensionSize { return 0, io.ErrShortBuffer } binary.BigEndian.PutUint64(buf[0:8], t.Timestamp) return absCaptureTimeExtensionSize, nil } // Marshal serializes the members to buffer. func (t AbsCaptureTimeExtension) Marshal() ([]byte, error) { if t.EstimatedCaptureClockOffset != nil { buf := make([]byte, absCaptureTimeExtendedExtensionSize) binary.BigEndian.PutUint64(buf[0:8], t.Timestamp) binary.BigEndian.PutUint64(buf[8:16], uint64(*t.EstimatedCaptureClockOffset)) // nolint: gosec // G115 return buf, nil } buf := make([]byte, absCaptureTimeExtensionSize) binary.BigEndian.PutUint64(buf[0:8], t.Timestamp) return buf, nil } // Unmarshal parses the passed byte slice and stores the result in the members. func (t *AbsCaptureTimeExtension) Unmarshal(rawData []byte) error { if len(rawData) < absCaptureTimeExtensionSize { return errTooSmall } t.Timestamp = binary.BigEndian.Uint64(rawData[0:8]) if len(rawData) >= absCaptureTimeExtendedExtensionSize { offset := int64(binary.BigEndian.Uint64(rawData[8:16])) // nolint: gosec // G115 false positive t.EstimatedCaptureClockOffset = &offset } return nil } // CaptureTime produces the estimated time.Time represented by this extension. func (t AbsCaptureTimeExtension) CaptureTime() time.Time { return toTime(t.Timestamp) } // EstimatedCaptureClockOffsetDuration produces the estimated time.Duration represented by this extension. func (t AbsCaptureTimeExtension) EstimatedCaptureClockOffsetDuration() *time.Duration { if t.EstimatedCaptureClockOffset == nil { return nil } offset := *t.EstimatedCaptureClockOffset negative := false if offset < 0 { offset = -offset negative = true } duration := time.Duration(offset/(1<<32))*time.Second + time.Duration((offset&0xFFFFFFFF)*1e9/(1<<32))*time.Nanosecond if negative { duration = -duration } return &duration } // NewAbsCaptureTimeExtension makes new AbsCaptureTimeExtension from time.Time. func NewAbsCaptureTimeExtension(captureTime time.Time) *AbsCaptureTimeExtension { return &AbsCaptureTimeExtension{ Timestamp: toNtpTime(captureTime), } } // NewAbsCaptureTimeExtensionWithCaptureClockOffset makes new AbsCaptureTimeExtension from time.Time and a clock offset. func NewAbsCaptureTimeExtensionWithCaptureClockOffset( captureTime time.Time, captureClockOffset time.Duration, ) *AbsCaptureTimeExtension { ns := captureClockOffset.Nanoseconds() negative := false if ns < 0 { ns = -ns negative = true } lsb := (ns / 1e9) & 0xFFFFFFFF msb := (((ns % 1e9) * (1 << 32)) / 1e9) & 0xFFFFFFFF offset := (lsb << 32) | msb if negative { offset = -offset } return &AbsCaptureTimeExtension{ Timestamp: toNtpTime(captureTime), EstimatedCaptureClockOffset: &offset, } } rtp-1.8.27/abscapturetimeextension_test.go000066400000000000000000000100321512154406600207510ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "io" "testing" "time" "github.com/stretchr/testify/assert" ) func TestAbsCaptureTimeExtension_Roundtrip(t *testing.T) { //nolint:cyclop t.Run("positive captureClockOffset", func(t *testing.T) { t0 := time.Now() e1 := NewAbsCaptureTimeExtension(t0) b1, err := e1.Marshal() assert.NoError(t, err) var o1 AbsCaptureTimeExtension assert.NoError(t, o1.Unmarshal(b1)) dt1 := o1.CaptureTime().Sub(t0).Seconds() assert.GreaterOrEqual(t, dt1, -0.001) assert.LessOrEqual(t, dt1, 0.001) assert.Nil(t, o1.EstimatedCaptureClockOffsetDuration()) e2 := NewAbsCaptureTimeExtensionWithCaptureClockOffset(t0, 1250*time.Millisecond) b2, err := e2.Marshal() assert.NoError(t, err) var o2 AbsCaptureTimeExtension assert.NoError(t, o2.Unmarshal(b2)) dt2 := o1.CaptureTime().Sub(t0).Seconds() assert.GreaterOrEqual(t, dt2, -0.001) assert.LessOrEqual(t, dt2, 0.001) assert.Equal(t, 1250*time.Millisecond, *o2.EstimatedCaptureClockOffsetDuration()) }) // This test can verify the for for the issue 247 t.Run("negative captureClockOffset", func(t *testing.T) { t0 := time.Now() e1 := NewAbsCaptureTimeExtension(t0) b1, err := e1.Marshal() assert.NoError(t, err) var o1 AbsCaptureTimeExtension assert.NoError(t, o1.Unmarshal(b1)) dt1 := o1.CaptureTime().Sub(t0).Seconds() assert.GreaterOrEqual(t, dt1, -0.001) assert.LessOrEqual(t, dt1, 0.001) assert.Nil(t, o1.EstimatedCaptureClockOffsetDuration()) e2 := NewAbsCaptureTimeExtensionWithCaptureClockOffset(t0, -250*time.Millisecond) b2, err := e2.Marshal() assert.NoError(t, err) var o2 AbsCaptureTimeExtension assert.NoError(t, o2.Unmarshal(b2)) dt2 := o1.CaptureTime().Sub(t0).Seconds() assert.GreaterOrEqual(t, dt2, -0.001) assert.LessOrEqual(t, dt2, 0.001) assert.Equal(t, -250*time.Millisecond, *o2.EstimatedCaptureClockOffsetDuration()) }) } func TestAbsCaptureTimeExtensionMarshalTo(t *testing.T) { t.Run("without offset", func(t *testing.T) { ext := NewAbsCaptureTimeExtension(time.Now()) buf := make([]byte, ext.MarshalSize()) n, err := ext.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, ext.MarshalSize(), n) expected, _ := ext.Marshal() assert.Equal(t, expected, buf) _, err = ext.MarshalTo(nil) assert.ErrorIs(t, err, io.ErrShortBuffer) }) t.Run("with offset", func(t *testing.T) { ext := NewAbsCaptureTimeExtensionWithCaptureClockOffset(time.Now(), 100*time.Millisecond) buf := make([]byte, ext.MarshalSize()) n, err := ext.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, ext.MarshalSize(), n) expected, _ := ext.Marshal() assert.Equal(t, expected, buf) _, err = ext.MarshalTo(nil) assert.ErrorIs(t, err, io.ErrShortBuffer) }) } //nolint:gochecknoglobals var ( absCaptureTimeSink []byte absCaptureTimeBuf = make([]byte, absCaptureTimeExtensionSize) absCaptureTimeExtendedBuf = make([]byte, absCaptureTimeExtendedExtensionSize) absCaptureTimeSinkInt int ) func BenchmarkAbsCaptureTimeExtension_Marshal(b *testing.B) { ext := NewAbsCaptureTimeExtension(time.Now()) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { absCaptureTimeSink, _ = ext.Marshal() } } func BenchmarkAbsCaptureTimeExtension_MarshalTo(b *testing.B) { ext := NewAbsCaptureTimeExtension(time.Now()) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { absCaptureTimeSinkInt, _ = ext.MarshalTo(absCaptureTimeBuf) } } func BenchmarkAbsCaptureTimeExtensionWithOffset_Marshal(b *testing.B) { ext := NewAbsCaptureTimeExtensionWithCaptureClockOffset(time.Now(), 100*time.Millisecond) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { absCaptureTimeSink, _ = ext.Marshal() } } func BenchmarkAbsCaptureTimeExtensionWithOffset_MarshalTo(b *testing.B) { ext := NewAbsCaptureTimeExtensionWithCaptureClockOffset(time.Now(), 100*time.Millisecond) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { absCaptureTimeSinkInt, _ = ext.MarshalTo(absCaptureTimeExtendedBuf) } } rtp-1.8.27/abssendtimeextension.go000066400000000000000000000051111512154406600172020ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "io" "time" ) const ( absSendTimeExtensionSize = 3 ) // AbsSendTimeExtension is a extension payload format in // http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time type AbsSendTimeExtension struct { Timestamp uint64 } // MarshalSize returns the size of the AbsSendTimeExtension once marshaled. func (t AbsSendTimeExtension) MarshalSize() int { return absSendTimeExtensionSize } // MarshalTo marshals the extension to the given buffer. // Returns io.ErrShortBuffer if buf is too small. func (t AbsSendTimeExtension) MarshalTo(buf []byte) (int, error) { if len(buf) < absSendTimeExtensionSize { return 0, io.ErrShortBuffer } buf[0] = byte(t.Timestamp & 0xFF0000 >> 16) buf[1] = byte(t.Timestamp & 0xFF00 >> 8) buf[2] = byte(t.Timestamp & 0xFF) return absSendTimeExtensionSize, nil } // Marshal serializes the members to buffer. func (t AbsSendTimeExtension) Marshal() ([]byte, error) { return []byte{ byte(t.Timestamp & 0xFF0000 >> 16), byte(t.Timestamp & 0xFF00 >> 8), byte(t.Timestamp & 0xFF), }, nil } // Unmarshal parses the passed byte slice and stores the result in the members. func (t *AbsSendTimeExtension) Unmarshal(rawData []byte) error { if len(rawData) < absSendTimeExtensionSize { return errTooSmall } t.Timestamp = uint64(rawData[0])<<16 | uint64(rawData[1])<<8 | uint64(rawData[2]) return nil } // Estimate absolute send time according to the receive time. // Note that if the transmission delay is larger than 64 seconds, estimated time will be wrong. func (t *AbsSendTimeExtension) Estimate(receive time.Time) time.Time { receiveNTP := toNtpTime(receive) ntp := receiveNTP&0xFFFFFFC000000000 | (t.Timestamp&0xFFFFFF)<<14 if receiveNTP < ntp { // Receive time must be always later than send time ntp -= 0x1000000 << 14 } return toTime(ntp) } // NewAbsSendTimeExtension makes new AbsSendTimeExtension from time.Time. func NewAbsSendTimeExtension(sendTime time.Time) *AbsSendTimeExtension { return &AbsSendTimeExtension{ Timestamp: toNtpTime(sendTime) >> 14, } } func toNtpTime(t time.Time) uint64 { var s uint64 var f uint64 u := uint64(t.UnixNano()) // nolint: gosec // G115 false positive s = u / 1e9 s += 0x83AA7E80 // offset in seconds between unix epoch and ntp epoch f = u % 1e9 f <<= 32 f /= 1e9 s <<= 32 return s | f } func toTime(t uint64) time.Time { s := t >> 32 f := t & 0xFFFFFFFF f *= 1e9 f >>= 32 s -= 0x83AA7E80 u := s*1e9 + f return time.Unix(0, int64(u)) // nolint: gosec // G115 false positive } rtp-1.8.27/abssendtimeextension_test.go000066400000000000000000000067031512154406600202510ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "io" "testing" "time" "github.com/stretchr/testify/assert" ) const absSendTimeResolution = 3800 * time.Nanosecond func TestNtpConversion(t *testing.T) { loc := time.FixedZone("UTC-5", -5*60*60) tests := []struct { t time.Time n uint64 }{ {t: time.Date(1985, time.June, 23, 4, 0, 0, 0, loc), n: 0xa0c65b1000000000}, {t: time.Date(1999, time.December, 31, 23, 59, 59, 500000, loc), n: 0xbc18084f0020c49b}, {t: time.Date(2019, time.March, 27, 13, 39, 30, 8675309, loc), n: 0xe04641e202388b88}, } for i, in := range tests { out := toNtpTime(in.t) assert.Equalf( t, in.n, out, "[%d] Converted NTP time from time.Time differs", i, ) } for i, in := range tests { out := toTime(in.n) diff := in.t.Sub(out) assert.GreaterOrEqualf( t, diff, -absSendTimeResolution, "[%d] Converted time.Time from NTP time differs", i, ) assert.LessOrEqual( t, diff, absSendTimeResolution, "[%d] Converted time.Time from NTP time differs", i, ) } } func TestAbsSendTimeExtension_Roundtrip(t *testing.T) { tests := []AbsSendTimeExtension{ { Timestamp: 123456, }, { Timestamp: 654321, }, } for i, in := range tests { b, err := in.Marshal() assert.NoError(t, err) var out AbsSendTimeExtension assert.NoError(t, out.Unmarshal(b)) assert.Equalf( t, in.Timestamp, out.Timestamp, "[%d] Timestamp differs", i, ) } } func TestAbsSendTimeExtension_Estimate(t *testing.T) { tests := []struct { sendNTP uint64 receiveNTP uint64 }{ // FFFFFFC000000000 mask of second {0xa0c65b1000100000, 0xa0c65b1001000000}, // not carried {0xa0c65b3f00000000, 0xa0c65b4001000000}, // carried during transmission } for i, in := range tests { inTime := toTime(in.sendNTP) send := &AbsSendTimeExtension{in.sendNTP >> 14} b, err := send.Marshal() assert.NoError(t, err) var received AbsSendTimeExtension assert.NoError(t, received.Unmarshal(b)) estimated := received.Estimate(toTime(in.receiveNTP)) diff := estimated.Sub(inTime) assert.GreaterOrEqualf( t, diff, -absSendTimeResolution, "[%d] Estimated time differs, expected: %v, estimated: %v (receive time: %v)", i, inTime.UTC(), estimated.UTC(), toTime(in.receiveNTP).UTC(), ) assert.LessOrEqual( t, diff, absSendTimeResolution, "[%d] Estimated time differs, expected: %v, estimated: %v (receive time: %v)", i, inTime.UTC(), estimated.UTC(), toTime(in.receiveNTP).UTC(), ) } } func TestAbsSendTimeExtensionMarshalTo(t *testing.T) { ext := AbsSendTimeExtension{Timestamp: 123456} buf := make([]byte, ext.MarshalSize()) n, err := ext.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, ext.MarshalSize(), n) expected, _ := ext.Marshal() assert.Equal(t, expected, buf) _, err = ext.MarshalTo(nil) assert.ErrorIs(t, err, io.ErrShortBuffer) } //nolint:gochecknoglobals var ( absSendTimeSink []byte absSendTimeBuf = make([]byte, absSendTimeExtensionSize) absSendTimeSinkInt int ) func BenchmarkAbsSendTimeExtension_Marshal(b *testing.B) { ext := AbsSendTimeExtension{Timestamp: 123456} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { absSendTimeSink, _ = ext.Marshal() } } func BenchmarkAbsSendTimeExtension_MarshalTo(b *testing.B) { ext := AbsSendTimeExtension{Timestamp: 123456} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { absSendTimeSinkInt, _ = ext.MarshalTo(absSendTimeBuf) } } rtp-1.8.27/audiolevelextension.go000066400000000000000000000045431512154406600170450ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "errors" "io" ) const ( // audioLevelExtensionSize One byte header size. audioLevelExtensionSize = 1 ) var errAudioLevelOverflow = errors.New("audio level overflow") // AudioLevelExtension is a extension payload format described in // https://tools.ietf.org/html/rfc6464 // // Implementation based on: // https://chromium.googlesource.com/external/webrtc/+/e2a017725570ead5946a4ca8235af27470ca0df9/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc#49 // // One byte format: // 0 1 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | len=0 |V| level | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Two byte format: // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | len=1 |V| level | 0 (pad) | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // //nolint:lll type AudioLevelExtension struct { Level uint8 Voice bool } // MarshalSize returns the size of the AudioLevelExtension once marshaled. func (a AudioLevelExtension) MarshalSize() int { return audioLevelExtensionSize } // MarshalTo marshals the extension to the given buffer. // Returns io.ErrShortBuffer if buf is too small. func (a AudioLevelExtension) MarshalTo(buf []byte) (int, error) { if a.Level > 127 { return 0, errAudioLevelOverflow } if len(buf) < audioLevelExtensionSize { return 0, io.ErrShortBuffer } voice := uint8(0x00) if a.Voice { voice = 0x80 } buf[0] = voice | a.Level return audioLevelExtensionSize, nil } // Marshal serializes the members to buffer. func (a AudioLevelExtension) Marshal() ([]byte, error) { if a.Level > 127 { return nil, errAudioLevelOverflow } voice := uint8(0x00) if a.Voice { voice = 0x80 } buf := make([]byte, audioLevelExtensionSize) buf[0] = voice | a.Level return buf, nil } // Unmarshal parses the passed byte slice and stores the result in the members. func (a *AudioLevelExtension) Unmarshal(rawData []byte) error { if len(rawData) < audioLevelExtensionSize { return errTooSmall } a.Level = rawData[0] & 0x7F a.Voice = rawData[0]&0x80 != 0 return nil } rtp-1.8.27/audiolevelextension_test.go000066400000000000000000000042171512154406600201020ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "io" "testing" "github.com/stretchr/testify/assert" ) func TestAudioLevelExtensionTooSmall(t *testing.T) { a := AudioLevelExtension{} rawData := []byte{} assert.ErrorIs(t, a.Unmarshal(rawData), errTooSmall) } func TestAudioLevelExtensionVoiceTrue(t *testing.T) { a1 := AudioLevelExtension{} rawData := []byte{ 0x88, } assert.NoError(t, a1.Unmarshal(rawData)) a2 := AudioLevelExtension{ Level: 8, Voice: true, } assert.Equal(t, a2, a1) dstData, _ := a2.Marshal() assert.Equal(t, rawData, dstData) } func TestAudioLevelExtensionVoiceFalse(t *testing.T) { a1 := AudioLevelExtension{} rawData := []byte{ 0x8, } assert.NoError(t, a1.Unmarshal(rawData)) a2 := AudioLevelExtension{ Level: 8, Voice: false, } assert.Equal(t, a2, a1) dstData, _ := a2.Marshal() assert.Equal(t, rawData, dstData) } func TestAudioLevelExtensionLevelOverflow(t *testing.T) { a := AudioLevelExtension{ Level: 128, Voice: false, } _, err := a.Marshal() assert.ErrorIs(t, err, errAudioLevelOverflow) _, err = a.MarshalTo(make([]byte, 10)) assert.ErrorIs(t, err, errAudioLevelOverflow) } func TestAudioLevelExtensionMarshalTo(t *testing.T) { a := AudioLevelExtension{Level: 8, Voice: true} buf := make([]byte, a.MarshalSize()) n, err := a.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, a.MarshalSize(), n) expected, _ := a.Marshal() assert.Equal(t, expected, buf) _, err = a.MarshalTo(nil) assert.ErrorIs(t, err, io.ErrShortBuffer) } //nolint:gochecknoglobals var ( audioLevelSink []byte audioLevelBuf = make([]byte, audioLevelExtensionSize) audioLevelSinkInt int ) func BenchmarkAudioLevelExtension_Marshal(b *testing.B) { ext := AudioLevelExtension{Level: 8, Voice: true} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { audioLevelSink, _ = ext.Marshal() } } func BenchmarkAudioLevelExtension_MarshalTo(b *testing.B) { ext := AudioLevelExtension{Level: 8, Voice: true} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { audioLevelSinkInt, _ = ext.MarshalTo(audioLevelBuf) } } rtp-1.8.27/codecov.yml000066400000000000000000000007151512154406600145720ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # SPDX-FileCopyrightText: 2023 The Pion community # 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/**/*" rtp-1.8.27/codecs/000077500000000000000000000000001512154406600136625ustar00rootroot00000000000000rtp-1.8.27/codecs/av1/000077500000000000000000000000001512154406600143515ustar00rootroot00000000000000rtp-1.8.27/codecs/av1/frame/000077500000000000000000000000001512154406600154435ustar00rootroot00000000000000rtp-1.8.27/codecs/av1/frame/av1.go000066400000000000000000000032261512154406600164640ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT // Package frame provides code to construct complete media frames from packetized media. package frame import "github.com/pion/rtp/codecs" // AV1 represents a collection of OBUs given a stream of AV1 Packets. // Each AV1 RTP Packet is a collection of OBU Elements. Each OBU Element may be a full OBU, or just a fragment of one. // AV1 provides the tools to construct a collection of OBUs from a collection of OBU Elements. This structure // contains an internal cache and should be used for the entire RTP Stream. type AV1 struct { // Buffer for fragmented OBU. If ReadFrames is called on a RTP Packet // that doesn't contain a fully formed OBU obuBuffer []byte } func (f *AV1) pushOBUElement(isFirstOBUFragment *bool, obuElement []byte, obuList [][]byte) [][]byte { if *isFirstOBUFragment { *isFirstOBUFragment = false // Discard pushed because we don't have a fragment to combine it with if f.obuBuffer == nil { return obuList } obuElement = append(f.obuBuffer, obuElement...) f.obuBuffer = nil } return append(obuList, obuElement) } // ReadFrames processes the codecs.AV1Packet and returns fully constructed frames. func (f *AV1) ReadFrames(pkt *codecs.AV1Packet) ([][]byte, error) { OBUs := [][]byte{} isFirstOBUFragment := pkt.Z for i := range pkt.OBUElements { OBUs = f.pushOBUElement(&isFirstOBUFragment, pkt.OBUElements[i], OBUs) } if pkt.Y && len(OBUs) > 0 { // Take copy of OBUElement that is being cached f.obuBuffer = append(f.obuBuffer, append([]byte{}, OBUs[len(OBUs)-1]...)...) OBUs = OBUs[:len(OBUs)-1] } return OBUs, nil } rtp-1.8.27/codecs/av1/frame/av1_test.go000066400000000000000000000046711512154406600175300ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package frame import ( "testing" "github.com/pion/rtp/codecs" "github.com/stretchr/testify/assert" ) // First is Fragment (and no buffer) // Self contained OBU // OBU spread across 3 packets. func TestAV1_ReadFrames(t *testing.T) { // First is Fragment of OBU, but no OBU Elements is cached fragm := &AV1{} frames, err := fragm.ReadFrames(&codecs.AV1Packet{Z: true, OBUElements: [][]byte{{0x01}}}) assert.NoError(t, err) assert.Equal(t, [][]byte{}, frames, "No frames should be generated") fragm = &AV1{} frames, err = fragm.ReadFrames(&codecs.AV1Packet{OBUElements: [][]byte{{0x01}}}) assert.NoError(t, err) assert.Equal(t, [][]byte{{0x01}}, frames, "One frame should be generated") fragm = &AV1{} frames, err = fragm.ReadFrames(&codecs.AV1Packet{Y: true, OBUElements: [][]byte{{0x00}}}) assert.NoError(t, err) assert.Equal(t, [][]byte{}, frames, "No frames should be generated") frames, err = fragm.ReadFrames(&codecs.AV1Packet{Z: true, OBUElements: [][]byte{{0x01}}}) assert.NoError(t, err) assert.Equal(t, [][]byte{{0x00, 0x01}}, frames, "One frame should be generated") } // Marshal some AV1 Frames to RTP, assert that AV1 can get them back in the original format. func TestAV1_ReadFrames_E2E(t *testing.T) { const mtu = 1500 frames := [][]byte{ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}, {0x00, 0x01}, { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, }, {0x00, 0x01}, } frames = append(frames, []byte{}) for i := 0; i <= 5; i++ { frames[len(frames)-1] = append( frames[len(frames)-1], []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}..., ) } frames = append(frames, []byte{}) for i := 0; i <= 500; i++ { frames[len(frames)-1] = append( frames[len(frames)-1], []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}..., ) } payloader := &codecs.AV1Payloader{} f := &AV1{} for _, originalFrame := range frames { for _, payload := range payloader.Payload(mtu, originalFrame) { rtpPacket := &codecs.AV1Packet{} _, err := rtpPacket.Unmarshal(payload) assert.NoError(t, err) decodedFrame, err := f.ReadFrames(rtpPacket) assert.NoError(t, err) if len(decodedFrame) != 0 { assert.Equal(t, originalFrame, decodedFrame[0]) } } } } rtp-1.8.27/codecs/av1/obu/000077500000000000000000000000001512154406600151365ustar00rootroot00000000000000rtp-1.8.27/codecs/av1/obu/errors.go000066400000000000000000000007431512154406600170050ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package obu import "errors" var ( // ErrInvalidOBUHeader is returned when an OBU header has forbidden bits set. ErrInvalidOBUHeader = errors.New("invalid OBU header") // ErrShortHeader is returned when an OBU header is not large enough. // This can happen when an extension header is expected but not present. ErrShortHeader = errors.New("OBU header is not large enough") ) rtp-1.8.27/codecs/av1/obu/leb128.go000066400000000000000000000033771512154406600164740ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT // Package obu implements tools for working with the Open Bitstream Unit. package obu import "errors" const ( sevenLsbBitmask = uint(0b01111111) msbBitmask = uint(0b10000000) ) // ErrFailedToReadLEB128 indicates that a buffer ended before a LEB128 value could be successfully read. var ErrFailedToReadLEB128 = errors.New("payload ended before LEB128 was finished") // EncodeLEB128 encodes a uint as LEB128. func EncodeLEB128(in uint) (out uint) { for { // Copy seven bits from in and discard // what we have copied from in out |= (in & sevenLsbBitmask) in >>= 7 // If we have more bits to encode set MSB // otherwise we are done if in != 0 { out |= msbBitmask out <<= 8 } else { return out } } } func decodeLEB128(in uint) (out uint) { for { // Take 7 LSB from in out |= (in & sevenLsbBitmask) // Discard the MSB in >>= 8 if in == 0 { return out } out <<= 7 } } // ReadLeb128 scans an buffer and decodes a Leb128 value. // If the end of the buffer is reached and all MSB are set // an error is returned. func ReadLeb128(in []byte) (uint, uint, error) { var encodedLength uint for i := range in { encodedLength |= uint(in[i]) if in[i]&byte(msbBitmask) == 0 { return decodeLEB128(encodedLength), uint(i + 1), nil // nolint: gosec // G115 } // Make more room for next read encodedLength <<= 8 } return 0, 0, ErrFailedToReadLEB128 } // WriteToLeb128 writes a uint to a LEB128 encoded byte slice. func WriteToLeb128(in uint) []byte { b := make([]byte, 10) for i := 0; i < len(b); i++ { b[i] = byte(in & 0x7f) in >>= 7 if in == 0 { return b[:i+1] } b[i] |= 0x80 } return b // unreachable } rtp-1.8.27/codecs/av1/obu/leb128_test.go000066400000000000000000000026001512154406600175170ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package obu import ( "encoding/hex" "fmt" "math" "testing" "github.com/stretchr/testify/assert" ) func TestLEB128(t *testing.T) { for _, test := range []struct { Value uint Encoded uint }{ {0, 0}, {5, 5}, {999999, 0xBF843D}, } { test := test encoded := EncodeLEB128(test.Value) assert.Equal(t, test.Encoded, encoded) decoded := decodeLEB128(encoded) assert.Equal(t, test.Value, decoded) } } func TestReadLeb128(t *testing.T) { _, _, err := ReadLeb128(nil) assert.ErrorIs(t, err, ErrFailedToReadLEB128, "ReadLeb128 on a nil buffer should return an error") _, _, err = ReadLeb128([]byte{0xFF}) assert.ErrorIs(t, err, ErrFailedToReadLEB128, "ReadLeb128 on a buffer with all MSB set should return an error") } func TestWriteToLeb128(t *testing.T) { type testVector struct { value uint leb128 string } testVectors := []testVector{ {150, "9601"}, {240, "f001"}, {400, "9003"}, {720, "d005"}, {1200, "b009"}, {999999, "bf843d"}, {0, "00"}, {math.MaxUint32, "ffffffff0f"}, } runTest := func(t *testing.T, v testVector) { t.Helper() b := WriteToLeb128(v.value) assert.Equal(t, v.leb128, hex.EncodeToString(b)) } for _, v := range testVectors { t.Run(fmt.Sprintf("encode %d", v.value), func(t *testing.T) { runTest(t, v) }) } } rtp-1.8.27/codecs/av1/obu/obu.go000066400000000000000000000126121512154406600162540ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package obu import ( "fmt" ) // Type represents the type of an AV1 OBU. type Type uint8 // OBU types as defined in the AV1 specification. // 5.3.1: https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=39 const ( // OBUSequenceHeader av1 sequence_header_obu. OBUSequenceHeader = Type(1) // OBUTemporalDelimiter av1 temporal_delimiter_obu. OBUTemporalDelimiter = Type(2) // OBUFrameHeader av1 frame_header_obu. OBUFrameHeader = Type(3) // OBUTileGroup av1 tile_group_obu. OBUTileGroup = Type(4) // OBUMetadata av1 metadata_obu. OBUMetadata = Type(5) // OBUFrame av1 frame_obu. OBUFrame = Type(6) // OBURedundantFrameHeader av1 redundant_frame_header_obu. OBURedundantFrameHeader = Type(7) // OBUTileList av1 tile_list_obu. OBUTileList = Type(8) // OBUPadding av1 padding_obu. OBUPadding = Type(15) ) //nolint:cyclop func (o Type) String() string { switch o { case OBUSequenceHeader: return "OBU_SEQUENCE_HEADER" case OBUTemporalDelimiter: return "OBU_TEMPORAL_DELIMITER" case OBUFrameHeader: return "OBU_FRAME_HEADER" case OBUTileGroup: return "OBU_TILE_GROUP" case OBUMetadata: return "OBU_METADATA" case OBUFrame: return "OBU_FRAME" case OBURedundantFrameHeader: return "OBU_REDUNDANT_FRAME_HEADER" case OBUTileList: return "OBU_TILE_LIST" case OBUPadding: return "OBU_PADDING" default: return "OBU_RESERVED" } } // Header represents the header of an OBU obu_header(). // 5.3.2: https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40 type Header struct { Type Type ExtensionHeader *ExtensionHeader HasSizeField bool Reserved1Bit bool } // ParseOBUHeader parses an OBU header from the given data. // 5.3.2: https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40 /* obu_header() { Type obu_forbidden_bit f(1) obu_type f(4) obu_extension_flag f(1) obu_has_size_field f(1) obu_reserved_1bit f(1) if ( obu_extension_flag == 1 ) obu_extension_header() } } */ func ParseOBUHeader(data []byte) (*Header, error) { if len(data) < 1 { return nil, fmt.Errorf("%w: data is too short", ErrShortHeader) } forbiddenBit := data[0] & 0x80 if forbiddenBit != 0 { return nil, fmt.Errorf("%w: forbidden bit is set", ErrInvalidOBUHeader) } obuType := Type((data[0] & 0x78) >> 3) obuExtensionFlag := (data[0] & 0x04) != 0 obuHasSizeField := (data[0] & 0x02) != 0 obuReserved1Bit := (data[0] & 0x01) != 0 header := &Header{ Type: obuType, HasSizeField: obuHasSizeField, Reserved1Bit: obuReserved1Bit, } if obuExtensionFlag { if len(data) < 2 { return nil, fmt.Errorf("%w: Unexpected end of data, expected extension header", ErrShortHeader) } extensionHeader := ParseOBUExtensionHeader(data[1]) header.ExtensionHeader = &extensionHeader } return header, nil } // Marshal serializes the OBU header to a byte slice. // If the OBU has an extension header, the extension header is serialized as well. // 5.3.2: https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40 /* obu_header() { Type obu_forbidden_bit f(1) obu_type f(4) obu_extension_flag f(1) obu_has_size_field f(1) obu_reserved_1bit f(1) if ( obu_extension_flag == 1 ) obu_extension_header() } } */ func (o *Header) Marshal() []byte { header := make([]byte, o.Size()) header[0] = (byte(o.Type) & 0x0f) << 3 if o.ExtensionHeader != nil { header[0] |= 0x04 header[1] = o.ExtensionHeader.Marshal() } if o.HasSizeField { header[0] |= 0x02 } if o.Reserved1Bit { header[0] |= 0x01 } return header } // Size returns the size of the OBU header in bytes. func (o *Header) Size() int { size := 1 if o.ExtensionHeader != nil { size++ } return size } // ExtensionHeader represents an OBU extension header obu_extension_header(). // 5.3.3 https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40 type ExtensionHeader struct { TemporalID uint8 SpatialID uint8 Reserved3Bits uint8 } // ParseOBUExtensionHeader parses an OBU extension header from the given data. // 5.3.3 https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40 /* obu_extension_header() { Type temporal_id f(3) spatial_id f(2) extension_header_reserved_3bits f(3) } */ func ParseOBUExtensionHeader(headerData byte) ExtensionHeader { return ExtensionHeader{ TemporalID: headerData >> 5, SpatialID: (headerData >> 3) & 0x03, Reserved3Bits: headerData & 0x07, } } // Marshal serializes the OBU extension header to a byte slice. // 5.3.3 https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40 /* obu_extension_header() { Type temporal_id f(3) spatial_id f(2) extension_header_reserved_3bits f(3) } */ func (o *ExtensionHeader) Marshal() byte { return (o.TemporalID << 5) | ((o.SpatialID & 0x3) << 3) | (o.Reserved3Bits & 0x07) } // OBU represents an AV1 OBU. // 5.1 https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=39 type OBU struct { Header Header Payload []byte } // Marshal serializes the OBU to low-overhead bitstream format. // 5.2 https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=40 func (o *OBU) Marshal() []byte { buffer := o.Header.Marshal() if o.Header.HasSizeField { buffer = append(buffer, WriteToLeb128(uint(len(o.Payload)))...) } return append(buffer, o.Payload...) } rtp-1.8.27/codecs/av1/obu/obu_test.go000066400000000000000000000175771512154406600173320ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package obu import ( "testing" "github.com/stretchr/testify/assert" ) func TestOBUType(t *testing.T) { for _, test := range []struct { Type Type TypeValue uint8 Str string }{ {OBUSequenceHeader, 1, "OBU_SEQUENCE_HEADER"}, {OBUTemporalDelimiter, 2, "OBU_TEMPORAL_DELIMITER"}, {OBUFrameHeader, 3, "OBU_FRAME_HEADER"}, {OBUTileGroup, 4, "OBU_TILE_GROUP"}, {OBUMetadata, 5, "OBU_METADATA"}, {OBUFrame, 6, "OBU_FRAME"}, {OBURedundantFrameHeader, 7, "OBU_REDUNDANT_FRAME_HEADER"}, {OBUTileList, 8, "OBU_TILE_LIST"}, {OBUPadding, 15, "OBU_PADDING"}, {Type(0), 0, "OBU_RESERVED"}, {Type(9), 9, "OBU_RESERVED"}, } { test := test assert.Equal(t, test.Str, test.Type.String()) assert.Equal(t, test.TypeValue, uint8(test.Type)) } } func TestOBUHeader_NoExtension(t *testing.T) { tests := []struct { Value byte Header Header }{ {0b0_0000_0_0_0, Header{Type: Type(0), HasSizeField: false, Reserved1Bit: false}}, {0b0_0001_0_0_0, Header{Type: OBUSequenceHeader, HasSizeField: false, Reserved1Bit: false}}, {0b0_0010_0_0_0, Header{Type: OBUTemporalDelimiter, HasSizeField: false, Reserved1Bit: false}}, {0b0_0011_0_0_0, Header{Type: OBUFrameHeader, HasSizeField: false, Reserved1Bit: false}}, {0b0_0100_0_0_0, Header{Type: OBUTileGroup, HasSizeField: false, Reserved1Bit: false}}, {0b0_0101_0_0_0, Header{Type: OBUMetadata, HasSizeField: false, Reserved1Bit: false}}, {0b0_0110_0_0_0, Header{Type: OBUFrame, HasSizeField: false, Reserved1Bit: false}}, {0b0_0111_0_0_0, Header{Type: OBURedundantFrameHeader, HasSizeField: false, Reserved1Bit: false}}, {0b0_1000_0_0_0, Header{Type: OBUTileList, HasSizeField: false, Reserved1Bit: false}}, {0b0_1111_0_0_0, Header{Type: OBUPadding, HasSizeField: false, Reserved1Bit: false}}, {0b0_1001_0_0_0, Header{Type: Type(9), HasSizeField: false, Reserved1Bit: false}}, {0b0_1001_0_1_0, Header{Type: Type(9), HasSizeField: true, Reserved1Bit: false}}, {0b0_1001_0_1_1, Header{Type: Type(9), HasSizeField: true, Reserved1Bit: true}}, {0b0_1001_0_0_1, Header{Type: Type(9), HasSizeField: false, Reserved1Bit: true}}, } for _, test := range tests { test := test buff := []byte{test.Value} header, err := ParseOBUHeader(buff) assert.NoError(t, err) assert.Equal(t, test.Header, *header) assert.Equal(t, 1, header.Size()) value := test.Header.Marshal() assert.Len(t, value, 1, "Expected size 1 for header without extension") assert.Equal(t, test.Value, value[0]) } } func TestOBUHeader_Extension(t *testing.T) { tests := []struct { HeaderValue byte Header Header ExtensionHeaderValue byte ExtensionHeader ExtensionHeader }{ { HeaderValue: 0b0_1001_1_0_0, Header: Header{Type: Type(9), HasSizeField: false, Reserved1Bit: false}, ExtensionHeaderValue: 0b001_01_000, ExtensionHeader: ExtensionHeader{TemporalID: 1, SpatialID: 1}, }, { HeaderValue: 0b0_1001_1_1_1, Header: Header{Type: Type(9), HasSizeField: true, Reserved1Bit: true}, ExtensionHeaderValue: 0b010_01_000, ExtensionHeader: ExtensionHeader{TemporalID: 2, SpatialID: 1}, }, { HeaderValue: 0b0_1001_1_1_1, Header: Header{Type: Type(9), HasSizeField: true, Reserved1Bit: true}, ExtensionHeaderValue: 0b011_01_000, ExtensionHeader: ExtensionHeader{TemporalID: 3, SpatialID: 1}, }, { HeaderValue: 0b0_1001_1_1_1, Header: Header{Type: Type(9), HasSizeField: true, Reserved1Bit: true}, ExtensionHeaderValue: 0b111_10_000, ExtensionHeader: ExtensionHeader{TemporalID: 7, SpatialID: 2}, }, { HeaderValue: 0b0_1001_1_1_1, Header: Header{Type: Type(9), HasSizeField: true, Reserved1Bit: true}, ExtensionHeaderValue: 0b111_11_001, ExtensionHeader: ExtensionHeader{TemporalID: 7, SpatialID: 3, Reserved3Bits: 1}, }, { HeaderValue: 0b0_1001_1_1_1, Header: Header{Type: Type(9), HasSizeField: true, Reserved1Bit: true}, ExtensionHeaderValue: 0b111_11_111, ExtensionHeader: ExtensionHeader{TemporalID: 7, SpatialID: 3, Reserved3Bits: 7}, }, } for _, test := range tests { test := test buff := []byte{test.HeaderValue, test.ExtensionHeaderValue} header, err := ParseOBUHeader(buff) assert.NoError(t, err) expected := Header{ Type: test.Header.Type, HasSizeField: test.Header.HasSizeField, Reserved1Bit: test.Header.Reserved1Bit, } assert.Equal(t, expected, test.Header) assert.Equal(t, 2, header.Size()) extension := header.ExtensionHeader assert.NotNil(t, extension) assert.Equal(t, test.ExtensionHeader, *extension) assert.Equal(t, test.ExtensionHeaderValue, extension.Marshal()) value := header.Marshal() assert.Lenf( t, value, 2, "Expected size 2 for header with extension, got %d", len(value), ) assert.Equal(t, buff, value) } } func TestOBUHeader_Short(t *testing.T) { _, err := ParseOBUHeader([]byte{}) assert.ErrorIs(t, err, ErrShortHeader) // Missing extension header _, err = ParseOBUHeader([]byte{0b0_0000_1_0_0}) assert.ErrorIs(t, err, ErrShortHeader) } func TestOBUHeader_Invalid(t *testing.T) { // forbidden bit is set _, err := ParseOBUHeader([]byte{0b1_0010_0_0_1}) assert.ErrorIs(t, err, ErrInvalidOBUHeader) } func TestOBUHeader_MarshalOutbound(t *testing.T) { // Marshal should turnicate the extension header values. header := Header{Type: Type(255)} assert.Equal(t, uint8(0b0_1111_000), header.Marshal()[0]) extentionHeader := ExtensionHeader{TemporalID: 255} assert.Equal(t, uint8(0b111_00_000), extentionHeader.Marshal()) extensionHeader := ExtensionHeader{SpatialID: 255} assert.Equal(t, uint8(0b000_11_000), extensionHeader.Marshal()) extensionHeader = ExtensionHeader{Reserved3Bits: 255} assert.Equal(t, uint8(0b000_00_111), extensionHeader.Marshal()) } func TestOBUMarshal(t *testing.T) { testOBU := OBU{ Header: Header{ Type: OBUFrame, HasSizeField: false, Reserved1Bit: false, }, Payload: []byte{0x01, 0x02, 0x03}, } data := testOBU.Marshal() assert.Len(t, data, 4) assert.Equal(t, testOBU.Header.Marshal()[0], data[0], "Expected header to be equal") assert.Equal(t, testOBU.Payload, data[1:]) } func TestOBUMarshal_ExtensionHeader(t *testing.T) { testOBU := OBU{ Header: Header{ Type: OBUFrame, HasSizeField: false, Reserved1Bit: false, ExtensionHeader: &ExtensionHeader{ TemporalID: 1, SpatialID: 2, }, }, Payload: []byte{0x01, 0x02, 0x03}, } data := testOBU.Marshal() assert.Len(t, data, 5) assert.Equal(t, testOBU.Header.Marshal()[0], data[0], "Expected header to be equal") assert.Equal(t, testOBU.Header.ExtensionHeader.Marshal(), data[1], "Expected extension header to equal") assert.Equal(t, testOBU.Payload, data[2:]) } func TestOBUMarshal_HasOBUSize(t *testing.T) { const payloadSize = 128 payload := make([]byte, payloadSize) for i := 0; i < payloadSize; i++ { payload[i] = byte(i) } testOBU := OBU{ Header: Header{ Type: OBUFrame, HasSizeField: true, Reserved1Bit: false, }, Payload: payload, } expected := append( testOBU.Header.Marshal(), append( // obu_size leb128 (128) []byte{0x80, 0x01}, testOBU.Payload..., )..., ) data := testOBU.Marshal() assert.Len(t, data, payloadSize+3) assert.Equal(t, testOBU.Header.Marshal()[0], data[0], "Expected header to be equal") assert.Equal(t, expected, data) } func TestOBUMarshal_ZeroPayload(t *testing.T) { testOBU := OBU{ Header: Header{ Type: OBUTemporalDelimiter, HasSizeField: false, }, } data := testOBU.Marshal() assert.Len(t, data, 1) testOBU = OBU{ Header: Header{ Type: OBUTemporalDelimiter, HasSizeField: true, }, } data = testOBU.Marshal() assert.Len(t, data, 2) assert.Equal(t, uint8(0), data[1], "Expected 0 for size") } rtp-1.8.27/codecs/av1_depacketizer.go000066400000000000000000000124071512154406600174360ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "fmt" "github.com/pion/rtp/codecs/av1/obu" ) // AV1Depacketizer is a AV1 RTP Packet depacketizer. // Reads AV1 packets from a RTP stream and outputs AV1 low overhead bitstream. type AV1Depacketizer struct { // holds the fragmented OBU from the previous packet. buffer []byte // Z, Y, N are flags from the AV1 Aggregation Header. Z, Y, N bool videoDepacketizer } // Unmarshal parses an AV1 RTP payload into its constituent OBUs stream with obu_size_field, // It assumes that the payload is in order (e.g. the caller is responsible for reordering RTP packets). // If the last OBU in the payload is fragmented, it will be stored in the buffer until the // it is completed. // //nolint:gocognit,cyclop func (d *AV1Depacketizer) Unmarshal(payload []byte) (buff []byte, err error) { buff = make([]byte, 0) if len(payload) <= 1 { return nil, errShortPacket } // |Z|Y| W |N|-|-|-| obuZ := (av1ZMask & payload[0]) != 0 // Z obuY := (av1YMask & payload[0]) != 0 // Y obuCount := (av1WMask & payload[0]) >> 4 // W obuN := (av1NMask & payload[0]) != 0 // N d.Z = obuZ d.Y = obuY d.N = obuN if obuN { d.buffer = nil } // Make sure we clear the buffer if Z is not 0. if !obuZ && len(d.buffer) > 0 { d.buffer = nil } obuOffset := 0 for offset := 1; offset < len(payload); obuOffset++ { isFirst := obuOffset == 0 isLast := obuCount != 0 && obuOffset == int(obuCount)-1 // https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header // W: two bit field that describes the number of OBU elements in the packet. // This field MUST be set equal to 0 or equal to the number of OBU elements contained in the packet. // If set to 0, each OBU element MUST be preceded by a length field. If not set to 0 // (i.e., W = 1, 2 or 3) the last OBU element MUST NOT be preceded by a length field. var lengthField, n int if obuCount == 0 || !isLast { obuSizeVal, nVal, err := obu.ReadLeb128(payload[offset:]) lengthField = int(obuSizeVal) //nolint:gosec // G115 false positive n = int(nVal) //nolint:gosec // G115 false positive if err != nil { return nil, err } offset += n if obuCount == 0 && offset+lengthField == len(payload) { isLast = true } } else { // https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header // Length of the last OBU element = // length of the RTP payload // - length of aggregation header // - length of previous OBU elements including length fields lengthField = len(payload) - offset } if offset+lengthField > len(payload) { return nil, fmt.Errorf( "%w: OBU size %d + %d offset exceeds payload length %d", errShortPacket, lengthField, offset, len(payload), ) } var obuBuffer []byte if isFirst && obuZ { // We lost the first fragment of the OBU // We drop the buffer and continue if len(d.buffer) == 0 { if isLast { break } offset += lengthField continue } obuBuffer = make([]byte, len(d.buffer)+lengthField) copy(obuBuffer, d.buffer) copy(obuBuffer[len(d.buffer):], payload[offset:offset+lengthField]) d.buffer = nil } else { obuBuffer = payload[offset : offset+lengthField] } offset += lengthField if isLast && obuY { d.buffer = obuBuffer break } if len(obuBuffer) == 0 { continue } obuHeader, err := obu.ParseOBUHeader(obuBuffer) if err != nil { return nil, err } // The temporal delimiter OBU, if present, SHOULD be removed when transmitting, // and MUST be ignored by receivers. Tile list OBUs are not supported. // They SHOULD be removed when transmitted, and MUST be ignored by receivers. // https://aomediacodec.github.io/av1-rtp-spec/#5-packetization-rules if obuHeader.Type == obu.OBUTemporalDelimiter || obuHeader.Type == obu.OBUTileList { continue } // obu_has_size_field should be set to 0 for AV1 RTP packets. // But we still check it to be sure, if we get obu size we just use it, instead of calculating it. if obuHeader.HasSizeField { obuSize, n, err := obu.ReadLeb128(obuBuffer[obuHeader.Size():]) if err != nil { return nil, err } // We validate the obu_size_field if it is present. sizeFromOBUSize := obuHeader.Size() + int(obuSize) + int(n) //nolint:gosec if lengthField != sizeFromOBUSize { return nil, fmt.Errorf( "%w: OBU size %d does not match calculated size %d", errShortPacket, obuSize, sizeFromOBUSize, ) } buff = append(buff, obuBuffer...) } else { obuHeader.HasSizeField = true buff = append(buff, obuHeader.Marshal()...) size := len(obuBuffer) - obuHeader.Size() buff = append(buff, obu.WriteToLeb128(uint(size))...) // nolint: gosec // G104 buff = append(buff, obuBuffer[obuHeader.Size():]...) } if isLast { break } } if obuCount != 0 && obuOffset != int(obuCount-1) { return nil, fmt.Errorf( "%w: OBU count %d does not match number of OBUs %d", errShortPacket, obuCount, obuOffset, ) } return buff, nil } // IsPartitionHead returns true if Z in the AV1 Aggregation Header // is set to 0. func (d *AV1Depacketizer) IsPartitionHead(payload []byte) bool { if len(payload) == 0 { return false } return (payload[0] & av1ZMask) == 0 } rtp-1.8.27/codecs/av1_depacketizer_test.go000066400000000000000000000410631512154406600204750ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "testing" "github.com/pion/rtp/codecs/av1/obu" "github.com/stretchr/testify/assert" ) // Create an AV1 OBU for testing. Returns one without the obu_size_field and another with it included. func createAV1OBU(obuType obu.Type, payload []byte) ([]byte, []byte) { header := obu.Header{Type: obuType} withoutSize := createTestPayload(header, payload) header.HasSizeField = true withSize := createTestPayload(header, payload) return withoutSize, withSize } func createTestPayload(obuHeader obu.Header, payload []byte) []byte { buf := make([]byte, 0) buf = append(buf, obuHeader.Marshal()...) if obuHeader.HasSizeField { buf = append(buf, obu.WriteToLeb128(uint(len(payload)))...) } buf = append(buf, payload...) return buf } func TestAV1Depacketizer_invalidPackets(t *testing.T) { depacketizer := AV1Depacketizer{} _, err := depacketizer.Unmarshal([]byte{}) assert.ErrorIs(t, err, errShortPacket) _, err = depacketizer.Unmarshal([]byte{0b11000000, 0xFF}) assert.ErrorIs(t, err, obu.ErrFailedToReadLEB128) _, err = depacketizer.Unmarshal(append([]byte{0b00000000}, obu.WriteToLeb128(0x99)...)) assert.ErrorIs(t, err, errShortPacket) _, err = depacketizer.Unmarshal(append([]byte{0b00000000}, obu.WriteToLeb128(0x01)...)) assert.ErrorIs(t, err, errShortPacket) _, err = depacketizer.Unmarshal( append( []byte{0b00110000}, append( obu.WriteToLeb128(1), []byte{0x01}..., )..., ), ) assert.ErrorIs(t, err, errShortPacket) } func TestAV1Depacketizer_singleOBU(t *testing.T) { payload := []byte{0x01, 0x02, 0x03} obuData, expectedOBU := createAV1OBU(4, payload) packet := make([]byte, 0) packet = append(packet, []byte{0b00000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obuData)))...) packet = append(packet, obuData...) d := AV1Depacketizer{} obu, err := d.Unmarshal(packet) assert.NoError(t, err) assert.Equal(t, expectedOBU, obu) } func TestAV1Depacketizer_singleOBUWithPadding(t *testing.T) { payload := []byte{0x01, 0x02, 0x03} obuData, expectedOBU := createAV1OBU(4, payload) packet := make([]byte, 0) packet = append(packet, []byte{0b00000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obuData)))...) packet = append(packet, obuData...) // padding packet = append(packet, []byte{0x00, 0x00, 0x00}...) d := AV1Depacketizer{} obu, err := d.Unmarshal(packet) assert.NoError(t, err) assert.Equal(t, expectedOBU, obu) } // AV1 OBUs shouldn't include the obu_size_field when packetized in RTP, // but we still support it since it's encountered in the wild (Including pion old clients). func TestAV1Depacketizer_withOBUSize(t *testing.T) { payload := []byte{0x01, 0x02, 0x03} _, obuData := createAV1OBU(4, payload) packet := make([]byte, 0) packet = append(packet, []byte{0b00000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obuData)))...) packet = append(packet, obuData...) d := AV1Depacketizer{} obu, err := d.Unmarshal(packet) assert.NoError(t, err) assert.Equal(t, obuData, obu) } func TestAV1Depacketizer_validateOBUSize(t *testing.T) { tests := []struct { name string payload []byte err error }{ { name: "invalid OBU size", payload: []byte{ 0, // Aggregation header 0x02, // Length field 0x22, // OBU header (has_size_field = 1) 0xFF, // Invalid LEB128 size }, err: obu.ErrFailedToReadLEB128, }, { name: "OBU size larger than payload", payload: []byte{ 0, // Aggregation header 0x05, // Length field 0x22, // OBU header (has_size_field = 1) 0x04, // LEB128 size 0x03, 0x01, 0x02, // OBU data }, err: errShortPacket, }, { name: "OBU size smaller than length field", payload: []byte{ 0, // Aggregation header 0x05, // Length field 0x22, // OBU header (has_size_field = 1) 0x02, // LEB128 size 0x03, 0x01, 0x02, // OBU data }, err: errShortPacket, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { d := AV1Depacketizer{} _, err := d.Unmarshal(tt.payload) assert.ErrorIs(t, err, tt.err) }) } } func TestAV1Depacketizer_dropBuffer(t *testing.T) { depacketizer := &AV1Depacketizer{} empty, err := depacketizer.Unmarshal([]byte{0x41, 0x02, 0x00, 0x01}) assert.NoError(t, err) assert.Len(t, empty, 0) payload := []byte{0x08, 0x02, 0x03} obuData, expectedOBU := createAV1OBU(4, payload) packet := make([]byte, 0) // N=true, should clear buffer packet = append(packet, []byte{0b00001000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obuData)))...) packet = append(packet, obuData...) obu, err := depacketizer.Unmarshal(packet) assert.NoError(t, err) assert.Equal(t, expectedOBU, obu) } func TestAV1Depacketizer_singleOBUWithW(t *testing.T) { payload := []byte{0x01, 0x02, 0x03} obuData, expectedOBU := createAV1OBU(4, payload) packet := append([]byte{0b00010000}, obuData...) d := AV1Depacketizer{} obu, err := d.Unmarshal(packet) assert.NoError(t, err) assert.Equal(t, expectedOBU, obu) } func TestDepacketizer_multipleFullOBUs(t *testing.T) { obu1, expectedOBU1 := createAV1OBU(4, []byte{0x01, 0x02, 0x03}) obu2, expectedOBU2 := createAV1OBU(4, []byte{0x04, 0x05, 0x06}) obu3, expectedOBU3 := createAV1OBU(4, []byte{0x07, 0x08, 0x09}) expected := append(append(expectedOBU1, expectedOBU2...), expectedOBU3...) packet := make([]byte, 0) packet = append(packet, []byte{0b00000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obu1)))...) packet = append(packet, obu1...) packet = append(packet, obu.WriteToLeb128(uint(len(obu2)))...) packet = append(packet, obu2...) packet = append(packet, obu.WriteToLeb128(uint(len(obu3)))...) packet = append(packet, obu3...) d := AV1Depacketizer{} obus, err := d.Unmarshal(packet) assert.NoError(t, err) assert.Equal(t, expected, obus) } func TestAV1Depacketizer_multipleFullOBUsWithW(t *testing.T) { obu1, expectedOBU1 := createAV1OBU(4, []byte{0x01, 0x02, 0x03}) obu2, expectedOBU2 := createAV1OBU(4, []byte{0x04, 0x05, 0x06}) obu3, expectedOBU3 := createAV1OBU(4, []byte{0x07, 0x08, 0x09}) expected := append(append(expectedOBU1, expectedOBU2...), expectedOBU3...) packet := make([]byte, 0) packet = append(packet, []byte{0b00110000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obu1)))...) packet = append(packet, obu1...) packet = append(packet, obu.WriteToLeb128(uint(len(obu2)))...) packet = append(packet, obu2...) // Last MUST NOT be preceded by a length field if W is not 0 packet = append(packet, obu3...) depacketizer := AV1Depacketizer{} obus, err := depacketizer.Unmarshal(packet) assert.NoError(t, err) assert.Equal(t, expected, obus) } func TestDepacketizer_fragmentedOBUS(t *testing.T) { // Not up to spec AV1 stream but it should be depacketized. // [ SH MD ] Frag(MD(0,0)) [ FH(0,0) TG(0,0) ] Frag(MD(0,1)) [ FH(0,1) ] [ TG(0,1) ] obu1, expectedOBU1 := createAV1OBU(1, []byte{0x01, 0x02, 0x03}) obu2, expectedOBU2 := createAV1OBU(7, []byte{0x04, 0x05, 0x06}) obu3, expectedOBU3 := createAV1OBU(7, []byte{0x07, 0x08, 0x09}) obu3f1 := obu3[:2] obu3f2 := obu3[2:] obu4, expectedOBU4 := createAV1OBU(3, []byte{0x0A, 0x0B, 0x0C}) obu5, expectedOBU5 := createAV1OBU(6, []byte{0x0D, 0x0E, 0x0F}) obu6, expectedOBU6 := createAV1OBU(7, []byte{0x10, 0x11, 0x12}) obu6f1 := obu6[:2] obu6f2 := obu6[2:] obu7, expectedOBU7 := createAV1OBU(3, []byte{0x13, 0x14, 0x15}) obu8, expectedOBU8 := createAV1OBU(6, []byte{0x16, 0x17, 0x18}) depacketizer := AV1Depacketizer{} packet := make([]byte, 0) packet = append(packet, []byte{0b01000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obu1)))...) packet = append(packet, obu1...) packet = append(packet, obu.WriteToLeb128(uint(len(obu2)))...) packet = append(packet, obu2...) packet = append(packet, obu.WriteToLeb128(uint(len(obu3f1)))...) packet = append(packet, obu3f1...) obus, err := depacketizer.Unmarshal(packet) assert.NoError(t, err) expected := make([]byte, 0) expected = append(expected, expectedOBU1...) expected = append(expected, expectedOBU2...) assert.Equal(t, expected, obus) packet = make([]byte, 0) packet = append(packet, []byte{0b11000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obu3f2)))...) packet = append(packet, obu3f2...) packet = append(packet, obu.WriteToLeb128(uint(len(obu4)))...) packet = append(packet, obu4...) packet = append(packet, obu.WriteToLeb128(uint(len(obu5)))...) packet = append(packet, obu5...) packet = append(packet, obu.WriteToLeb128(uint(len(obu6f1)))...) packet = append(packet, obu6f1...) obus, err = depacketizer.Unmarshal(packet) assert.NoError(t, err) expected = append(append(expectedOBU3, expectedOBU4...), expectedOBU5...) assert.Equal(t, expected, obus) packet = make([]byte, 0) packet = append(packet, []byte{0b10100000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obu6f2)))...) packet = append(packet, obu6f2...) // W is defined as 2, so the last OBU MUST NOT have a length field packet = append(packet, obu7...) obus, err = depacketizer.Unmarshal(packet) assert.NoError(t, err) expected = make([]byte, 0) expected = append(expected, expectedOBU6...) expected = append(expected, expectedOBU7...) assert.Equal(t, expected, obus) packet = make([]byte, 0) packet = append(packet, []byte{0b00000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obu8)))...) packet = append(packet, obu8...) obus, err = depacketizer.Unmarshal(packet) assert.NoError(t, err) assert.Equal(t, expectedOBU8, obus) } func TestAV1Depacketizer_dropLostFragment(t *testing.T) { depacketizer := AV1Depacketizer{} obus, err := depacketizer.Unmarshal( append( append([]byte{0b01000000}, obu.WriteToLeb128(3)...), []byte{0x01, 0x02, 0x03}..., ), ) assert.NoError(t, err) assert.Len(t, obus, 0, "Expected empty OBU for fragmented OBU") newOBU, expected := createAV1OBU(obu.OBUTileGroup, []byte{0x04, 0x05, 0x06}) obus, err = depacketizer.Unmarshal( append( append([]byte{0b00000000}, obu.WriteToLeb128(uint(len(newOBU)))...), newOBU..., ), ) assert.NoError(t, err) assert.Equal(t, expected, obus) } func TestAV1Depacketizer_dropIfLostFragment(t *testing.T) { depacketizer := AV1Depacketizer{} obus, err := depacketizer.Unmarshal( append( append([]byte{0b10000000}, obu.WriteToLeb128(3)...), []byte{0x01, 0x02, 0x03}..., ), ) assert.NoError(t, err) assert.Len(t, obus, 0, "Expected empty OBU for fragmented OBU") newOBU, expected := createAV1OBU(obu.OBUTileGroup, []byte{0x04, 0x05, 0x06}) obus, err = depacketizer.Unmarshal( append( append([]byte{0b00000000}, obu.WriteToLeb128(uint(len(newOBU)))...), newOBU..., ), ) assert.NoError(t, err) assert.Equal(t, expected, obus) packet := make([]byte, 0) packet = append(packet, []byte{0b10000000}...) packet = append(packet, obu.WriteToLeb128(3)...) packet = append(packet, []byte{0x01, 0x02, 0x03}...) packet = append(packet, obu.WriteToLeb128(uint(len(newOBU)))...) packet = append(packet, newOBU...) obus, err = depacketizer.Unmarshal(packet) assert.NoError(t, err) assert.Equal(t, expected, obus) } func TestAV1Depacketizer_IsPartitionTail(t *testing.T) { depacketizer := &AV1Depacketizer{ buffer: []byte{1, 2}, } assert.False(t, depacketizer.IsPartitionTail(false, []byte{1, 2})) assert.Equal(t, depacketizer.buffer, []byte{1, 2}) assert.True(t, depacketizer.IsPartitionTail(true, []byte{1, 2})) } func TestAV1Depacketizer_IsPartitionHead(t *testing.T) { depacketizer := &AV1Depacketizer{} assert.False(t, depacketizer.IsPartitionHead(nil)) assert.False(t, depacketizer.IsPartitionHead([]byte{})) assert.False(t, depacketizer.IsPartitionHead([]byte{0b11000000})) assert.True(t, depacketizer.IsPartitionHead([]byte{0b00000000})) } func TestAV1Depacketizer_ignoreBadOBUs(t *testing.T) { shouldIgnore := []obu.Type{ obu.OBUTemporalDelimiter, obu.OBUTileList, } for _, obuType := range shouldIgnore { payload := []byte{0x01, 0x02, 0x03} obuData, _ := createAV1OBU(obuType, payload) packet := make([]byte, 0) packet = append(packet, []byte{0b00000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obuData)))...) packet = append(packet, obuData...) depacketizer := AV1Depacketizer{} obu, err := depacketizer.Unmarshal(packet) assert.NoError(t, err) assert.Len(t, obu, 0, "Expected empty payload for OBU type %d", obuType) } } func TestAV1Depacketizer_fragmentedOverMultiple(t *testing.T) { fullOBU, expected := createAV1OBU( obu.OBUTileGroup, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, ) obuf1 := fullOBU[:2] obuf2 := fullOBU[2:5] obuf3 := fullOBU[5:7] obuf4 := fullOBU[7:] depacketizer := AV1Depacketizer{} packet := make([]byte, 0) packet = append(packet, []byte{0b01000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obuf1)))...) packet = append(packet, obuf1...) obus, err := depacketizer.Unmarshal(packet) assert.NoError(t, err) assert.Len(t, obus, 0, "Expected empty OBU for fragmented OBU") packet = make([]byte, 0) packet = append(packet, []byte{0b11000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obuf2)))...) packet = append(packet, obuf2...) obus, err = depacketizer.Unmarshal(packet) assert.NoError(t, err) assert.Len(t, obus, 0, "Expected empty OBU for fragmented OBU") packet = make([]byte, 0) packet = append(packet, []byte{0b11000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obuf3)))...) packet = append(packet, obuf3...) obus, err = depacketizer.Unmarshal(packet) assert.NoError(t, err) assert.Len(t, obus, 0, "Expected empty OBU for fragmented OBU") packet = make([]byte, 0) packet = append(packet, []byte{0b10000000}...) packet = append(packet, obu.WriteToLeb128(uint(len(obuf4)))...) packet = append(packet, obuf4...) obus, err = depacketizer.Unmarshal(packet) assert.NoError(t, err) assert.Equal(t, expected, obus) } func TestAV1Depacketizer_shortOBUHeader(t *testing.T) { d := AV1Depacketizer{} payload, err := d.Unmarshal([]byte{0x00, 0x01, 0x04}) assert.Error(t, err) assert.Len(t, payload, 0, "Expected empty payload for short OBU header") } func TestAV1Depacketizer_aggregationHeader(t *testing.T) { depacketizer := AV1Depacketizer{} tests := []struct { name string input []byte payload []byte Z, Y, N bool }{ { name: "Z=0, Y=0, N=0", // aggregation header = 0, length field = 1, obu header = 0x30 input: []byte{0x00, 0x01, 0x30}, // obu header = 0x32, obu size = 0 payload: []byte{0x32, 0x00}, }, { name: "Z=1, Y=0, N=0", // aggregation header = z = 1, length field = 1, obu header = 0x20 input: []byte{0x80, 0x01, 0x20}, // packet is fragmented, with missing previous packet, so the result is empty payload: []byte{}, Z: true, }, { name: "Z=0, Y=1, N=0", // aggregation header = Y = 1, length field = 1, obu header = 0x20 input: []byte{0x40, 0x01, 0x04}, // Packet is fragmented with the next packet. payload: []byte{}, Y: true, }, { name: "Z=0, Y=0, N=1", // aggregation header = N = 1, length field = 1, obu header = 0x30 input: []byte{0x08, 0x01, 0x30}, // obu header = 0x32, obu size = 0 payload: []byte{0x32, 0x00}, N: true, }, { name: "Z=1, Y=1, N=1", // aggregation header = N, Y, Z = 1, length field = 1, obu header = 0x30 input: []byte{0xC8, 0x01, 0x30}, // Packet is fragmented no payload. payload: []byte{}, Z: true, Y: true, N: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { payload, err := depacketizer.Unmarshal(tt.input) assert.NoError(t, err) assert.Equal(t, tt.payload, payload) assert.Equal(t, tt.Z, depacketizer.Z) assert.Equal(t, tt.Y, depacketizer.Y) assert.Equal(t, tt.N, depacketizer.N) }) } } func FuzzAV1DepacketizerUnmarshal(f *testing.F) { f.Add([]byte{0x10, 0x01, 0x00}) f.Add([]byte{0x20, 0x01, 0x00, 0x01, 0x00}) f.Add([]byte{0x00, 0x01, 0x00}) f.Add([]byte{0x80, 0x01, 0x00}) f.Add([]byte{0x40, 0x01, 0x00}) f.Add([]byte{0x08, 0x01, 0x00}) f.Add([]byte{0xC0, 0x01, 0x00}) f.Add([]byte{0x30, 0x01, 0x00, 0x01, 0x00, 0x00}) obuData, _ := createAV1OBU(obu.OBUFrameHeader, []byte{0x01, 0x02, 0x03}) packet := append([]byte{0x00}, obu.WriteToLeb128(uint(len(obuData)))...) packet = append(packet, obuData...) f.Add(packet) obuData2, _ := createAV1OBU(obu.OBUFrame, []byte{0x04, 0x05}) packet2 := append([]byte{0x10}, obuData2...) f.Add(packet2) // just check for crashes :) f.Fuzz(func(t *testing.T, data []byte) { depacketizer := &AV1Depacketizer{} _, err := depacketizer.Unmarshal(data) _ = err }) } rtp-1.8.27/codecs/av1_packet.go000066400000000000000000000306061512154406600162340ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "github.com/pion/rtp/codecs/av1/obu" ) const ( av1ZMask = byte(0b10000000) av1ZBitshift = 7 av1YMask = byte(0b01000000) av1YBitshift = 6 av1WMask = byte(0b00110000) av1WBitshift = 4 av1NMask = byte(0b00001000) av1NBitshift = 3 ) // AV1Payloader payloads AV1 packets. type AV1Payloader struct{} // Payload implements AV1 RTP payloader. // Reads from a open_bitstream_unit (OBU) framing stream as defined in // 5.3. https://aomediacodec.github.io/av1-spec/av1-spec.pdf#page=39 // Returns AV1 RTP packets https://aomediacodec.github.io/av1-rtp-spec/ // The payload is fragmented into multiple packets, each packet is a valid AV1 RTP payload. // nolint:cyclop func (p *AV1Payloader) Payload(mtu uint16, payload []byte) (payloads [][]byte) { // 2 is the minimum MTU for AV1 (aggregate header + 1 byte) if mtu <= 1 || len(payload) == 0 { return payloads } // We maximize the use of the W field in the AV1 aggregation header // to minimize the need for explicit length fields for each OBU. // To achieve this, we temporarily hold the OBU payload before adding it to a packet. // Since we can't determine in advance whether the next OBU should be included in the same packet // or start a new one, we also can't know ahead of time if an OBU is the last in the current packet. var currentOBUPayload []byte var currentPacketOBUHeader *obu.ExtensionHeader obusInPacket := 0 newSequence := false startWithNewPacket := false for offset := 0; offset < len(payload); { obuHeader, err := obu.ParseOBUHeader(payload[offset:]) if err != nil { break } offset += obuHeader.Size() // if ( obu_has_size_field ) { // obu_size leb128() // } else { // obu_size = sz - 1 - obu_extension_flag // } var obuSize int if obuHeader.HasSizeField { obuSizeValue, n, err := obu.ReadLeb128(payload[offset:]) if err != nil { break } offset += int(n) //nolint:gosec // G115, leb128 size is a signle digit obuSize = int(obuSizeValue) //nolint:gosec // G115, Leb128 is capped at 4 bytes } else { obuSize = len(payload) - offset } // Each RTP packet MUST NOT contain OBUs that belong to different temporal units. // If a sequence header OBU is present in an RTP packet, then it SHOULD be the first OBU in the packet. // https://aomediacodec.github.io/av1-rtp-spec/#5-packetization-rules needNewPacket := obuHeader.Type == obu.OBUTemporalDelimiter || obuHeader.Type == obu.OBUSequenceHeader // If more than one OBU contained in an RTP packet has an OBU extension header, // then the values of the temporal_id and spatial_id MUST be the same in all such OBUs in the RTP packet. if !needNewPacket && obuHeader.ExtensionHeader != nil && currentPacketOBUHeader != nil { needNewPacket = obuHeader.ExtensionHeader.SpatialID != currentPacketOBUHeader.SpatialID || obuHeader.ExtensionHeader.TemporalID != currentPacketOBUHeader.TemporalID } if obuHeader.ExtensionHeader != nil { currentPacketOBUHeader = obuHeader.ExtensionHeader } if obuSize > len(payload)-offset { break } if len(currentOBUPayload) > 0 { payloads, obusInPacket = p.appendOBUPayload( payloads, currentOBUPayload, newSequence, needNewPacket, startWithNewPacket, int(mtu), obusInPacket, ) currentOBUPayload = nil startWithNewPacket = needNewPacket if needNewPacket { newSequence = false currentPacketOBUHeader = nil } } // The temporal delimiter OBU, if present, SHOULD be removed when transmitting, // and MUST be ignored by receivers. Tile list OBUs are not supported. // They SHOULD be removed when transmitted, and MUST be ignored by receivers. // https://aomediacodec.github.io/av1-rtp-spec/#5-packetization-rules if obuHeader.Type == obu.OBUTileList || obuHeader.Type == obu.OBUTemporalDelimiter { offset += obuSize continue } currentOBUPayload = make([]byte, obuSize+obuHeader.Size()) // The AV1 specification allows OBUs to have an optional size field called obu_size // (also leb128 encoded), signaled by the obu_has_size_field flag in the OBU header. // To minimize overhead, the obu_has_size_field flag SHOULD be set to zero in all OBUs. // https://aomediacodec.github.io/av1-rtp-spec/#45-payload-structure obuHeader.HasSizeField = false copy(currentOBUPayload, obuHeader.Marshal()) //nolint:gosec // G115 we validate the size of the payload copy(currentOBUPayload[obuHeader.Size():], payload[offset:offset+obuSize]) offset += obuSize newSequence = obuHeader.Type == obu.OBUSequenceHeader } if len(currentOBUPayload) > 0 { payloads, _ = p.appendOBUPayload( payloads, currentOBUPayload, newSequence, true, startWithNewPacket, int(mtu), obusInPacket, ) } return payloads } //nolint:cyclop func (p *AV1Payloader) appendOBUPayload( payloads [][]byte, obuPayload []byte, isNewVideoSequence, isLast, startWithNewPacket bool, mtu, currentOBUCount int, ) ([][]byte, int) { currentPayload := len(payloads) - 1 freeSpace := 0 if currentPayload >= 0 { freeSpace = mtu - len(payloads[currentPayload]) } if currentPayload < 0 || freeSpace <= 0 || startWithNewPacket { payload := make([]byte, 1, mtu) if isNewVideoSequence { payload[0] |= 1 << av1NBitshift } payloads = append(payloads, payload) currentPayload = len(payloads) - 1 // MTU - aggregation header freeSpace = mtu - 1 currentOBUCount = 0 } remaining := len(obuPayload) // How much to write to the current packet. toWrite := min(remaining, freeSpace) // W: two bit field that describes the number of OBU elements in the packet. // This field MUST be set equal to 0 or equal to the number of OBU elements contained in the packet. // If set to 0, each OBU element MUST be preceded by a length field. If not set to 0 (i.e., W = 1, 2 or 3) // the last OBU element MUST NOT be preceded by a length field. // https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header shouldUseWField := (isLast || toWrite >= freeSpace) && currentOBUCount < 3 switch { case shouldUseWField: payloads[currentPayload][0] |= byte((currentOBUCount+1)<= 2: // 2 bytes is the minimum size for OBUs with length field. // [1 byte for the length field] [1 byte for the OBU] //nolint:gosec // G115 false positive toWrite = p.computeWriteSize(toWrite, freeSpace) lengthField := obu.WriteToLeb128(uint(toWrite)) //nolint:gosec // G115 false positive payloads[currentPayload] = append(payloads[currentPayload], lengthField...) payloads[currentPayload] = append(payloads[currentPayload], obuPayload[:toWrite]...) currentOBUCount++ default: // If we can't fit any more OBUs in the current packet (only 1 byte left and W=0) toWrite = 0 } obuPayload = obuPayload[toWrite:] remaining -= toWrite // Handle fragments. for remaining > 0 { // New packet with empty aggregation header. payload := make([]byte, 1, mtu) payloads = append(payloads, payload) currentPayload++ // Append the Y bit to the previous packet. And Z bit to the current packet. // If we wrote some bytes to the previous packet. // Handles an edge case where the previous packet has only one byte remaining, // while the W field is not used. This results in insufficient space // for a one-byte length field and a one-byte OBU. // So we don't write anything to the initial packet. if toWrite != 0 { payloads[currentPayload-1][0] |= av1YMask payloads[currentPayload][0] |= av1ZMask } toWrite = min(remaining, // MTU - aggregation header mtu-1) // Last OBU in the current packet, Or this whole packet is a fragment. if isLast || remaining >= mtu-1 { payloads[currentPayload][0] |= 1 << av1WBitshift } else { toWrite = p.computeWriteSize(toWrite, mtu-1) lengthField := obu.WriteToLeb128(uint(toWrite)) //nolint:gosec // G115 false positive payloads[currentPayload] = append(payloads[currentPayload], lengthField...) } payloads[currentPayload] = append(payloads[currentPayload], obuPayload[:toWrite]...) obuPayload = obuPayload[toWrite:] remaining -= toWrite currentOBUCount = 1 } return payloads, currentOBUCount } // Measure the maximum write size for a payload with leb128 encoding added. func (p *AV1Payloader) computeWriteSize(wantToWrite, canWrite int) int { leb128Size, isAtEdge := p.leb128Size(wantToWrite) if canWrite >= wantToWrite+leb128Size { return wantToWrite } // Handle edge case where subtracting one from the leb128 size // results in a smaller leb128 size that can fit in the remaining space. if isAtEdge && canWrite >= wantToWrite+leb128Size-1 { return wantToWrite - 1 } return wantToWrite - leb128Size } func (p *AV1Payloader) leb128Size(leb128 int) (size int, isAtEge bool) { switch { case leb128 >= 268435456: // 2^28 return 5, leb128 == 268435456 case leb128 >= 2097152: // 2^21 return 4, leb128 == 2097152 case leb128 >= 16384: // 2^14 return 3, leb128 == 16384 case leb128 >= 128: // 2^7 return 2, leb128 == 128 default: return 1, false } } // AV1Packet represents a depacketized AV1 RTP Packet /* * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * |Z|Y| W |N|-|-|-| * +-+-+-+-+-+-+-+-+ **/ // https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header // Deprecated: Use AV1Depacketizer instead. type AV1Packet struct { // Z: MUST be set to 1 if the first OBU element is an // OBU fragment that is a continuation of an OBU fragment // from the previous packet, and MUST be set to 0 otherwise. Z bool // Y: MUST be set to 1 if the last OBU element is an OBU fragment // that will continue in the next packet, and MUST be set to 0 otherwise. Y bool // W: two bit field that describes the number of OBU elements in the packet. // This field MUST be set equal to 0 or equal to the number of OBU elements // contained in the packet. If set to 0, each OBU element MUST be preceded by // a length field. If not set to 0 (i.e., W = 1, 2 or 3) the last OBU element // MUST NOT be preceded by a length field. Instead, the length of the last OBU // element contained in the packet can be calculated as follows: // Length of the last OBU element = // length of the RTP payload // - length of aggregation header // - length of previous OBU elements including length fields W byte // N: MUST be set to 1 if the packet is the first packet of a coded video sequence, and MUST be set to 0 otherwise. N bool // Each AV1 RTP Packet is a collection of OBU Elements. Each OBU Element may be a full OBU, or just a fragment of one. // AV1Frame provides the tools to construct a collection of OBUs from a collection of OBU Elements OBUElements [][]byte // zeroAllocation prevents populating the OBUElements field zeroAllocation bool } // Unmarshal parses the passed byte slice and stores the result in the AV1Packet this method is called upon. func (p *AV1Packet) Unmarshal(payload []byte) ([]byte, error) { if payload == nil { return nil, errNilPacket } else if len(payload) < 2 { return nil, errShortPacket } p.Z = ((payload[0] & av1ZMask) >> av1ZBitshift) != 0 p.Y = ((payload[0] & av1YMask) >> av1YBitshift) != 0 p.N = ((payload[0] & av1NMask) >> av1NBitshift) != 0 p.W = (payload[0] & av1WMask) >> av1WBitshift if p.Z && p.N { return nil, errIsKeyframeAndFragment } if !p.zeroAllocation { obuElements, err := p.parseBody(payload[1:]) if err != nil { return nil, err } p.OBUElements = obuElements } return payload[1:], nil } func (p *AV1Packet) parseBody(payload []byte) ([][]byte, error) { if p.OBUElements != nil { return p.OBUElements, nil } obuElements := [][]byte{} var obuElementLength, bytesRead uint currentIndex := uint(0) for i := 1; ; i++ { if currentIndex == uint(len(payload)) { break } // If W bit is set the last OBU Element will have no length header if byte(i) == p.W { bytesRead = 0 obuElementLength = uint(len(payload)) - currentIndex } else { var err error obuElementLength, bytesRead, err = obu.ReadLeb128(payload[currentIndex:]) if err != nil { return nil, err } } currentIndex += bytesRead if uint(len(payload)) < currentIndex+obuElementLength { return nil, errShortPacket } obuElements = append(obuElements, payload[currentIndex:currentIndex+obuElementLength]) currentIndex += obuElementLength } return obuElements, nil } rtp-1.8.27/codecs/av1_packet_test.go000066400000000000000000001432361512154406600172770ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "fmt" "testing" "github.com/pion/rtp/codecs/av1/obu" "github.com/stretchr/testify/assert" ) type testAV1AggregationHeader struct { Z, Y, N bool W byte } func (t testAV1AggregationHeader) Marshal() []byte { header := byte(0) if t.Z { header |= 0b10000000 } if t.Y { header |= 0b01000000 } if t.N { header |= 0b00001000 } header |= (t.W << 4) & 0b00110000 return []byte{header} } type testAV1OBUPayload struct { Payload []byte Header *obu.Header HasRTPLengthField bool } func (t testAV1OBUPayload) Marshal() []byte { payload := make([]byte, 0) // obu_size_field() leb128() var obuSize []byte if t.Header != nil && t.Header.HasSizeField { obuSize = obu.WriteToLeb128(uint(len(t.Payload))) } // RTP length field leb128() if t.HasRTPLengthField { length := len(t.Payload) + len(obuSize) if t.Header != nil { length += t.Header.Size() } payload = append(payload, obu.WriteToLeb128( uint(length), //nolint:gosec // G115 false positive )...) } if t.Header != nil { payload = append(payload, t.Header.Marshal()...) if t.Header.HasSizeField { payload = append(payload, obuSize...) } } payload = append(payload, t.Payload...) return payload } type testAV1MultiOBUsPayload []testAV1OBUPayload func (t testAV1MultiOBUsPayload) Marshal() []byte { payload := make([]byte, 0) for _, obu := range t { payload = append(payload, obu.Marshal()...) } return payload } type testAV1Tests struct { Name string MTU uint16 InputPayload []byte OutputPayloads [][]byte } func testAV1TestRun(t *testing.T, tests []testAV1Tests) { t.Helper() payloader := &AV1Payloader{} for _, test := range tests { t.Run(test.Name, func(t *testing.T) { result := payloader.Payload(test.MTU, test.InputPayload) assert.Equal(t, len(test.OutputPayloads), len(result)) for i := range result { assert.Equal(t, test.OutputPayloads[i], result[i]) } }) } } func TestAV1Payloader_ShortMtU(t *testing.T) { p := &AV1Payloader{} assert.Len(t, p.Payload(0, []byte{0x00, 0x01, 0x18}), 0, "Expected empty payload") assert.Len(t, p.Payload(1, []byte{0x00, 0x01, 0x18}), 0, "Expected empty payload") // 2 is the minimum MTU for AV1 (aggregate header + 1 byte) assert.Greater(t, len(p.Payload(2, []byte{0x00, 0x01, 0x18})), 0) } func TestAV1Payloader_SinglePacket(t *testing.T) { tests := []testAV1Tests{ { Name: "Single Sequence Header", MTU: 1000, InputPayload: (testAV1OBUPayload{ Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, }, Header: &obu.Header{ Type: obu.OBUSequenceHeader, HasSizeField: false, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ N: true, W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUSequenceHeader, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, }, }).Marshal()..., ), }, }, { Name: "Single Frame", MTU: 1000, InputPayload: (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, }, }).Marshal()..., ), }, }, { "Should remove size field", 1000, (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, }, }).Marshal(), [][]byte{ append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, }, }).Marshal()..., ), }, }, { Name: "Should Skip Tile List", MTU: 1000, InputPayload: (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUTileList, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, }, }).Marshal(), }, } testAV1TestRun(t, tests) } //nolint:maintidx func TestAV1Payloader_MultipleOBUsInSinglePacket(t *testing.T) { tests := []testAV1Tests{ { Name: "Should pack two OBUs in a single packet with W=2", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 2, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal()..., ), }, }, { Name: "Should pack three OBUs in a single packet with W=3", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 3, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, }, }).Marshal()..., ), }, }, { Name: "Should pack four OBUs in a single packet with W=0", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x10, 0x11, 0x12, 0x13, 0x14}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{}).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x10, 0x11, 0x12, 0x13, 0x14}, }, }).Marshal()..., ), }, }, { Name: "Should pack five OBUs in a single packet with W=0", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x10, 0x11, 0x12, 0x13, 0x14}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x15, 0x16, 0x17, 0x18, 0x19}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{}).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x10, 0x11, 0x12, 0x13, 0x14}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x15, 0x16, 0x17, 0x18, 0x19}, }, }).Marshal()..., ), }, }, { Name: "Should read last obu without obu_size_field", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 2, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal()..., ), }, }, } testAV1TestRun(t, tests) } //nolint:maintidx func TestAV1Payloader_HandleMTUBasedFragmentation(t *testing.T) { tests := []testAV1Tests{ { Name: "Should pack two OBUs in a single packet with W=1 for each", MTU: 7, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }).Marshal()..., ), }, }, { Name: "Should split OBU over two packets with each W=1", MTU: 7, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, }, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, Y: true, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, Z: true, }).Marshal(), (testAV1OBUPayload{ Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }).Marshal()..., ), }, }, { Name: "Should split OBU over three packets with each W=1", MTU: 7, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, }, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, Y: true, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, Z: true, Y: true, }).Marshal(), (testAV1OBUPayload{ Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, Z: true, }).Marshal(), (testAV1OBUPayload{ Payload: []byte{0x0C, 0x0D, 0x0E, 0x0F}, }).Marshal()..., ), }, }, { Name: "Should split OBU over three packets and adds extra packet", MTU: 7, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, }, }, { Header: &obu.Header{ Type: obu.OBUFrame, }, Payload: []byte{ 0x01, 0x02, }, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, Y: true, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, Z: true, Y: true, }).Marshal(), (testAV1OBUPayload{ Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 2, Z: true, }).Marshal(), (testAV1OBUPayload{ Payload: append( (testAV1OBUPayload{ Payload: []byte{0x0C}, HasRTPLengthField: true, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrame, }, Payload: []byte{0x01, 0x02}, }).Marshal()..., ), }).Marshal()..., ), }, }, { Name: "Should skip the last byte in the packet if W=0", MTU: 14, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x02}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x03}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x04}, }, { Header: &obu.Header{ Type: obu.OBUFrame, HasSizeField: true, }, Payload: []byte{0x05, 0x06, 0x07, 0x08, 0x09}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{}).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x01}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x02}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x03}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x04}, }, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{Type: obu.OBUFrame}, Payload: []byte{0x05, 0x06, 0x07, 0x08, 0x09}, }).Marshal()..., ), }, }, { Name: "Should split OBU after four OBUs in a single packet with W=0", MTU: 15, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x02}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x03}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x04}, }, { Header: &obu.Header{ Type: obu.OBUFrame, HasSizeField: true, }, Payload: []byte{0x05, 0x06, 0x07, 0x08, 0x09}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ Y: true, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x01}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x02}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x03}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x04}, }, { // only the length field and the header. HasRTPLengthField: true, Header: &obu.Header{ Type: obu.OBUFrame, }, }, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, Z: true, }).Marshal(), (testAV1OBUPayload{ Payload: []byte{0x05, 0x06, 0x07, 0x08, 0x09}, }).Marshal()..., ), }, }, { Name: "Should use the correct W size when OBUs ands at the MTU boundary", MTU: 9, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x02}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x03}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x04}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x06}, }, { Header: &obu.Header{ Type: obu.OBUFrame, HasSizeField: true, }, Payload: []byte{0x07, 0x08, 0x09}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 3, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x01}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x02}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x03}, }, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 3, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x04}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: []byte{0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x06}, }, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrame, }, Payload: []byte{0x07, 0x08, 0x09}, }).Marshal()..., ), }, }, { Name: "Should use the correct W size for the next OBU when OBU fragment ands at the MTU boundary", MTU: 9, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, }, }, { Header: &obu.Header{ Type: obu.OBUFrame, HasSizeField: true, }, Payload: []byte{0x10, 0x11, 0x12}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, Y: true, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, }).Marshal()..., ), append( (testAV1AggregationHeader{ Z: true, W: 1, }).Marshal(), (testAV1OBUPayload{ Payload: []byte{0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrame, }, Payload: []byte{0x10, 0x11, 0x12}, }).Marshal()..., ), }, }, { Name: "Should split OBU over three packets and adds extra fragmented packet", MTU: 7, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, }, }, { Header: &obu.Header{ Type: obu.OBUFrame, }, Payload: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, }, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, Y: true, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, Z: true, Y: true, }).Marshal(), (testAV1OBUPayload{ Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 2, Z: true, Y: true, }).Marshal(), (testAV1OBUPayload{ Payload: append( (testAV1OBUPayload{ Payload: []byte{0x0C, 0x0D, 0x0E}, HasRTPLengthField: true, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrame, }, Payload: []byte{0x01}, }).Marshal()..., ), }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, Z: true, }).Marshal(), (testAV1OBUPayload{ Payload: []byte{0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), }, }, } testAV1TestRun(t, tests) } func TestAV1Payloader_TemporalDelimiter(t *testing.T) { tests := []testAV1Tests{ { Name: "Ignore single temporal delimiter", MTU: 1000, InputPayload: (testAV1OBUPayload{ Header: &obu.Header{Type: obu.OBUTemporalDelimiter}, Payload: []byte{}, }).Marshal(), OutputPayloads: [][]byte{}, }, { Name: "Ignore mutlitple temporal delimiters", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUTemporalDelimiter, HasSizeField: true, }, }, { Header: &obu.Header{Type: obu.OBUTemporalDelimiter}, }, }).Marshal(), }, { Name: "Split payloads at temporal delimiter", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUTemporalDelimiter, HasSizeField: true, }, }, { Header: &obu.Header{Type: obu.OBUFrame}, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{Type: obu.OBUFrame}, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }).Marshal()..., ), }, }, } testAV1TestRun(t, tests) } func TestAV1Payloader_ExtensionHeaders(t *testing.T) { tests := []testAV1Tests{ { Name: "Keeps extension headers", MTU: 1000, InputPayload: (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, ExtensionHeader: &obu.ExtensionHeader{ TemporalID: 1, SpatialID: 2, }, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, ExtensionHeader: &obu.ExtensionHeader{ TemporalID: 1, SpatialID: 2, }, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), }, }, { Name: "Keeps OBUs with the same temporal ID and spatial ID in the same packet", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, ExtensionHeader: &obu.ExtensionHeader{ TemporalID: 1, SpatialID: 2, }, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrame, ExtensionHeader: &obu.ExtensionHeader{ TemporalID: 1, SpatialID: 2, }, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 2, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, ExtensionHeader: &obu.ExtensionHeader{ TemporalID: 1, SpatialID: 2, }, }, HasRTPLengthField: true, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrame, ExtensionHeader: &obu.ExtensionHeader{ TemporalID: 1, SpatialID: 2, }, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal()..., ), }, }, { Name: "Split OBUs with different temporal ID and spatial ID to different packets", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, ExtensionHeader: &obu.ExtensionHeader{ TemporalID: 1, SpatialID: 2, }, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrame, ExtensionHeader: &obu.ExtensionHeader{ TemporalID: 2, SpatialID: 1, }, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, ExtensionHeader: &obu.ExtensionHeader{ TemporalID: 1, SpatialID: 2, }, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrame, ExtensionHeader: &obu.ExtensionHeader{ TemporalID: 2, SpatialID: 1, }, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal()..., ), }, }, } testAV1TestRun(t, tests) } func TestAV1Payloader_SequenceHeader(t *testing.T) { tests := []testAV1Tests{ { Name: "Should pack sequence header with frame in a single packet", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUSequenceHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 2, N: true, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUSequenceHeader, }, HasRTPLengthField: true, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal()..., ), }, }, { Name: "Sequence header should start a new packet", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUSequenceHeader, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, N: true, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUSequenceHeader, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }).Marshal()..., ), }, }, { Name: "Sequence header should start a new packet and break with temporal delimiter", MTU: 1000, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }, { Header: &obu.Header{ Type: obu.OBUSequenceHeader, HasSizeField: true, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }, { Header: &obu.Header{ Type: obu.OBUTemporalDelimiter, HasSizeField: true, }, }, { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, N: true, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUSequenceHeader, }, Payload: []byte{0x06, 0x07, 0x08, 0x09, 0x0A}, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, }).Marshal()..., ), }, }, } testAV1TestRun(t, tests) } func TestAv1Payloader_FragmentedEdgeLeb128Size(t *testing.T) { size := uint16(128) payload := make([]byte, 0, size) for i := uint16(0); i < size; i++ { payload = append(payload, byte(i)) } tests := []testAV1Tests{ { Name: fmt.Sprintf("Should handle leb128 size edge case at %d bytes", size), MTU: (size * 5) + 14, InputPayload: (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: payload, }, { Header: &obu.Header{ Type: obu.OBUFrame, HasSizeField: true, }, Payload: payload, }, { Header: &obu.Header{ Type: obu.OBUFrame, HasSizeField: true, }, Payload: payload, }, { Header: &obu.Header{ Type: obu.OBUFrame, HasSizeField: true, }, Payload: payload, }, { Header: &obu.Header{ Type: obu.OBUFrame, }, Payload: payload[:size-1], }, }).Marshal(), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ Y: true, }).Marshal(), (testAV1MultiOBUsPayload{ { Header: &obu.Header{ Type: obu.OBUFrameHeader, }, HasRTPLengthField: true, Payload: payload, }, { Header: &obu.Header{ Type: obu.OBUFrame, }, HasRTPLengthField: true, Payload: payload, }, { Header: &obu.Header{ Type: obu.OBUFrame, }, HasRTPLengthField: true, Payload: payload, }, { Header: &obu.Header{ Type: obu.OBUFrame, }, HasRTPLengthField: true, Payload: payload, }, { Header: &obu.Header{ Type: obu.OBUFrame, }, HasRTPLengthField: true, Payload: payload[:size-2], }, }).Marshal()..., ), append( (testAV1AggregationHeader{ W: 1, Z: true, }).Marshal(), (testAV1OBUPayload{ Payload: payload[size-2 : size-1], }).Marshal()..., ), }, }, } testAV1TestRun(t, tests) } func TestAV1Payloader_ReturnEarlyOnError(t *testing.T) { tests := []testAV1Tests{ { Name: "Should return early on empty payload", MTU: 1000, InputPayload: []byte{}, }, { Name: "Should return early on nil payload", MTU: 1000, InputPayload: nil, }, { Name: "Should return early on invalid OBU (missing extension header)", MTU: 1000, InputPayload: []byte{0x04}, }, { Name: "Should return early on invalid OBU (invalid obu_size leb128)", MTU: 1000, InputPayload: []byte{0x4a, 0xff}, }, { Name: "Should return early on small packets (obu_size is bigger than the payload)", MTU: 1000, InputPayload: append( (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal(), append( (&obu.Header{ Type: obu.OBUFrame, HasSizeField: true, }).Marshal(), 0x03, )..., ), OutputPayloads: [][]byte{ append( (testAV1AggregationHeader{ W: 1, }).Marshal(), (testAV1OBUPayload{ Header: &obu.Header{ Type: obu.OBUFrameHeader, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, }).Marshal()..., ), }, }, } testAV1TestRun(t, tests) } func TestAV1Payloader_Leb128Size(t *testing.T) { tests := []struct { leb128 int size int edge bool }{ {0, 1, false}, {1, 1, false}, {127, 1, false}, {128, 2, true}, {16383, 2, false}, {16384, 3, true}, {2097151, 3, false}, {2097152, 4, true}, {268435455, 4, false}, {268435456, 5, true}, } payloader := &AV1Payloader{} for _, test := range tests { actual, edge := payloader.leb128Size(test.leb128) assert.Equal(t, test.size, actual) assert.Equal(t, test.edge, edge) } } func TestAV1_depacketizer_to_packetizer(t *testing.T) { type testOBU struct { Type obu.Type Size uint64 } obus := []testOBU{ {Type: obu.OBUSequenceHeader, Size: 10}, {Type: obu.OBUFrameHeader, Size: 20}, {Type: obu.OBUFrame, Size: 3000}, {Type: obu.OBUFrame, Size: 4800}, {Type: obu.OBUFrame, Size: 3024}, {Type: obu.OBUFrame, Size: 2841}, {Type: obu.OBUFrameHeader, Size: 20}, {Type: obu.OBUFrame, Size: 8000}, {Type: obu.OBUSequenceHeader, Size: 12}, {Type: obu.OBUFrameHeader, Size: 20}, {Type: obu.OBUFrame, Size: 6300}, {Type: obu.OBUFrame, Size: 53}, {Type: obu.OBUFrame, Size: 101}, {Type: obu.OBUFrame, Size: 202}, {Type: obu.OBUSequenceHeader, Size: 11}, {Type: obu.OBUFrameHeader, Size: 20}, {Type: obu.OBUFrame, Size: 9000}, } payload := make([]byte, 0) for _, testOBU := range obus { header := obu.Header{ Type: testOBU.Type, HasSizeField: true, } payload = append(payload, header.Marshal()...) payload = append(payload, obu.WriteToLeb128(uint(testOBU.Size))...) for j := 0; j < int(testOBU.Size); j++ { //nolint:gosec // G115 payload = append(payload, byte((j+len(payload))%256)) } } mtuSize := []uint16{ 32, 215, 1500, 8192, 9216, } for _, mtu := range mtuSize { t.Run(fmt.Sprintf("MTU %d", mtu), func(t *testing.T) { payloader := &AV1Payloader{} depacketizer := &AV1Depacketizer{} result := make([]byte, 0) packets := payloader.Payload(mtu, payload) for _, packet := range packets { p, err := depacketizer.Unmarshal(packet) assert.NoError(t, err) assert.GreaterOrEqual(t, int(mtu), len(packet), "Expected packet size to be smaller or equal to %d", mtu) result = append(result, p...) } assert.Equalf( t, len(payload), len(result), "Expected to packetize and depacketize to be the same for MTU=%d", mtu, ) assert.Equalf(t, payload, result, "Expected to packetize and depacketize to be the same for MTU=%d", mtu) }) } } func TestAV1_Unmarshal_Error(t *testing.T) { for _, test := range []struct { expectedError error input []byte }{ {errNilPacket, nil}, {errShortPacket, []byte{0x00}}, {errIsKeyframeAndFragment, []byte{byte(0b10001000), 0x00}}, {obu.ErrFailedToReadLEB128, []byte{byte(0b10000000), 0xFF, 0xFF}}, {errShortPacket, []byte{byte(0b10000000), 0xFF, 0x0F, 0x00, 0x00}}, } { test := test av1Pkt := &AV1Packet{} _, err := av1Pkt.Unmarshal(test.input) assert.ErrorIs(t, err, test.expectedError) } } func TestAV1_Unmarshal(t *testing.T) { // nolint: dupl av1Payload := []byte{ 0x68, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x2c, 0xd6, 0xd3, 0x0c, 0xd5, 0x02, 0x00, 0x80, 0x30, 0x10, 0xc3, 0xc0, 0x07, 0xff, 0xff, 0xf8, 0xb7, 0x30, 0xc0, 0x00, 0x00, 0x88, 0x17, 0xf9, 0x0c, 0xcf, 0xc6, 0x7b, 0x9c, 0x0d, 0xda, 0x55, 0x82, 0x82, 0x67, 0x2f, 0xf0, 0x07, 0x26, 0x5d, 0xf6, 0xc6, 0xe3, 0x12, 0xdd, 0xf9, 0x71, 0x77, 0x43, 0xe6, 0xba, 0xf2, 0xce, 0x36, 0x08, 0x63, 0x92, 0xac, 0xbb, 0xbd, 0x26, 0x4c, 0x05, 0x52, 0x91, 0x09, 0xf5, 0x37, 0xb5, 0x18, 0xbe, 0x5c, 0x95, 0xb1, 0x2c, 0x13, 0x27, 0x81, 0xc2, 0x52, 0x8c, 0xaf, 0x27, 0xca, 0xf2, 0x93, 0xd6, 0x2e, 0x46, 0x32, 0xed, 0x71, 0x87, 0x90, 0x1d, 0x0b, 0x84, 0x46, 0x7f, 0xd1, 0x57, 0xc1, 0x0d, 0xc7, 0x5b, 0x41, 0xbb, 0x8a, 0x7d, 0xe9, 0x2c, 0xae, 0x36, 0x98, 0x13, 0x39, 0xb9, 0x0c, 0x66, 0x47, 0x05, 0xa2, 0xdf, 0x55, 0xc4, 0x09, 0xab, 0xe4, 0xfb, 0x11, 0x52, 0x36, 0x27, 0x88, 0x86, 0xf3, 0x4a, 0xbb, 0xef, 0x40, 0xa7, 0x85, 0x2a, 0xfe, 0x92, 0x28, 0xe4, 0xce, 0xce, 0xdc, 0x4b, 0xd0, 0xaa, 0x3c, 0xd5, 0x16, 0x76, 0x74, 0xe2, 0xfa, 0x34, 0x91, 0x4f, 0xdc, 0x2b, 0xea, 0xae, 0x71, 0x36, 0x74, 0xe1, 0x2a, 0xf3, 0xd3, 0x53, 0xe8, 0xec, 0xd6, 0x63, 0xf6, 0x6a, 0x75, 0x95, 0x68, 0xcc, 0x99, 0xbe, 0x17, 0xd8, 0x3b, 0x87, 0x5b, 0x94, 0xdc, 0xec, 0x32, 0x09, 0x18, 0x4b, 0x37, 0x58, 0xb5, 0x67, 0xfb, 0xdf, 0x66, 0x6c, 0x16, 0x9e, 0xba, 0x72, 0xc6, 0x21, 0xac, 0x02, 0x6d, 0x6b, 0x17, 0xf9, 0x68, 0x22, 0x2e, 0x10, 0xd7, 0xdf, 0xfb, 0x24, 0x69, 0x7c, 0xaf, 0x11, 0x64, 0x80, 0x7a, 0x9d, 0x09, 0xc4, 0x1f, 0xf1, 0xd7, 0x3c, 0x5a, 0xc2, 0x2c, 0x8e, 0xf5, 0xff, 0xee, 0xc2, 0x7c, 0xa1, 0xe4, 0xcb, 0x1c, 0x6d, 0xd8, 0x15, 0x0e, 0x40, 0x36, 0x85, 0xe7, 0x04, 0xbb, 0x64, 0xca, 0x6a, 0xd9, 0x21, 0x8e, 0x95, 0xa0, 0x83, 0x95, 0x10, 0x48, 0xfa, 0x00, 0x54, 0x90, 0xe9, 0x81, 0x86, 0xa0, 0x4a, 0x6e, 0xbe, 0x9b, 0xf0, 0x73, 0x0a, 0x17, 0xbb, 0x57, 0x81, 0x17, 0xaf, 0xd6, 0x70, 0x1f, 0xe8, 0x6d, 0x32, 0x59, 0x14, 0x39, 0xd8, 0x1d, 0xec, 0x59, 0xe4, 0x98, 0x4d, 0x44, 0xf3, 0x4f, 0x7b, 0x47, 0xd9, 0x92, 0x3b, 0xd9, 0x5c, 0x98, 0xd5, 0xf1, 0xc9, 0x8b, 0x9d, 0xb1, 0x65, 0xb3, 0xe1, 0x87, 0xa4, 0x6a, 0xcc, 0x42, 0x96, 0x66, 0xdb, 0x5f, 0xf9, 0xe1, 0xa1, 0x72, 0xb6, 0x05, 0x02, 0x1f, 0xa3, 0x14, 0x3e, 0xfe, 0x99, 0x7f, 0xeb, 0x42, 0xcf, 0x76, 0x09, 0x19, 0xd2, 0xd2, 0x99, 0x75, 0x1c, 0x67, 0xda, 0x4d, 0xf4, 0x87, 0xe5, 0x55, 0x8b, 0xed, 0x01, 0x82, 0xf6, 0xd6, 0x1c, 0x5c, 0x05, 0x96, 0x96, 0x79, 0xc1, 0x61, 0x87, 0x74, 0xcd, 0x29, 0x83, 0x27, 0xae, 0x47, 0x87, 0x36, 0x34, 0xab, 0xc4, 0x73, 0x76, 0x58, 0x1b, 0x4a, 0xec, 0x0e, 0x4c, 0x2f, 0xb1, 0x76, 0x08, 0x7f, 0xaf, 0xfa, 0x6d, 0x8c, 0xde, 0xe4, 0xae, 0x58, 0x87, 0xe7, 0xa0, 0x27, 0x05, 0x0d, 0xf5, 0xa7, 0xfb, 0x2a, 0x75, 0x33, 0xd9, 0x3b, 0x65, 0x60, 0xa4, 0x13, 0x27, 0xa5, 0xe5, 0x1b, 0x83, 0x78, 0x7a, 0xd7, 0xec, 0x0c, 0xed, 0x8b, 0xe6, 0x4e, 0x8f, 0xfe, 0x6b, 0x5d, 0xbb, 0xa8, 0xee, 0x38, 0x81, 0x6f, 0x09, 0x23, 0x08, 0x8f, 0x07, 0x21, 0x09, 0x39, 0xf0, 0xf8, 0x03, 0x17, 0x24, 0x2a, 0x22, 0x44, 0x84, 0xe1, 0x5c, 0xf3, 0x4f, 0x20, 0xdc, 0xc1, 0xe7, 0xeb, 0xbc, 0x0b, 0xfb, 0x7b, 0x20, 0x66, 0xa4, 0x27, 0xe2, 0x01, 0xb3, 0x5f, 0xb7, 0x47, 0xa1, 0x88, 0x4b, 0x8c, 0x47, 0xda, 0x36, 0x98, 0x60, 0xd7, 0x46, 0x92, 0x0b, 0x7e, 0x5b, 0x4e, 0x34, 0x50, 0x12, 0x67, 0x50, 0x8d, 0xe7, 0xc9, 0xe4, 0x96, 0xef, 0xae, 0x2b, 0xc7, 0xfa, 0x36, 0x29, 0x05, 0xf5, 0x92, 0xbd, 0x62, 0xb7, 0xbb, 0x90, 0x66, 0xe0, 0xad, 0x14, 0x3e, 0xe7, 0xb4, 0x24, 0xf3, 0x04, 0xcf, 0x22, 0x14, 0x86, 0xa4, 0xb8, 0xfb, 0x83, 0x56, 0xce, 0xaa, 0xb4, 0x87, 0x5a, 0x9e, 0xf2, 0x0b, 0xaf, 0xad, 0x40, 0xe1, 0xb5, 0x5c, 0x6b, 0xa7, 0xee, 0x9f, 0xbb, 0x1a, 0x68, 0x4d, 0xc3, 0xbf, 0x22, 0x4d, 0xbe, 0x58, 0x52, 0xc9, 0xcc, 0x0d, 0x88, 0x04, 0xf1, 0xf8, 0xd4, 0xfb, 0xd6, 0xad, 0xcf, 0x13, 0x84, 0xd6, 0x2f, 0x90, 0x0c, 0x5f, 0xb4, 0xe2, 0xd8, 0x29, 0x26, 0x8d, 0x7c, 0x6b, 0xab, 0x91, 0x91, 0x3c, 0x25, 0x39, 0x9c, 0x86, 0x08, 0x39, 0x54, 0x59, 0x0d, 0xa4, 0xa8, 0x31, 0x9f, 0xa3, 0xbc, 0xc2, 0xcb, 0xf9, 0x30, 0x49, 0xc3, 0x68, 0x0e, 0xfc, 0x2b, 0x9f, 0xce, 0x59, 0x02, 0xfa, 0xd4, 0x4e, 0x11, 0x49, 0x0d, 0x93, 0x0c, 0xae, 0x57, 0xd7, 0x74, 0xdd, 0x13, 0x1a, 0x15, 0x79, 0x10, 0xcc, 0x99, 0x32, 0x9b, 0x57, 0x6d, 0x53, 0x75, 0x1f, 0x6d, 0xbb, 0xe4, 0xbc, 0xa9, 0xd4, 0xdb, 0x06, 0xe7, 0x09, 0xb0, 0x6f, 0xca, 0xb3, 0xb1, 0xed, 0xc5, 0x0b, 0x8d, 0x8e, 0x70, 0xb0, 0xbf, 0x8b, 0xad, 0x2f, 0x29, 0x92, 0xdd, 0x5a, 0x19, 0x3d, 0xca, 0xca, 0xed, 0x05, 0x26, 0x25, 0xee, 0xee, 0xa9, 0xdd, 0xa0, 0xe3, 0x78, 0xe0, 0x56, 0x99, 0x2f, 0xa1, 0x3f, 0x07, 0x5e, 0x91, 0xfb, 0xc4, 0xb3, 0xac, 0xee, 0x07, 0xa4, 0x6a, 0xcb, 0x42, 0xae, 0xdf, 0x09, 0xe7, 0xd0, 0xbb, 0xc6, 0xd4, 0x38, 0x58, 0x7d, 0xb4, 0x45, 0x98, 0x38, 0x21, 0xc8, 0xc1, 0x3c, 0x81, 0x12, 0x7e, 0x37, 0x03, 0xa8, 0xcc, 0xf3, 0xf9, 0xd9, 0x9d, 0x8f, 0xc1, 0xa1, 0xcc, 0xc1, 0x1b, 0xe3, 0xa8, 0x93, 0x91, 0x2c, 0x0a, 0xe8, 0x1f, 0x28, 0x13, 0x44, 0x07, 0x68, 0x5a, 0x8f, 0x27, 0x41, 0x18, 0xc9, 0x31, 0xc4, 0xc1, 0x71, 0xe2, 0xf0, 0xc4, 0xf4, 0x1e, 0xac, 0x29, 0x49, 0x2f, 0xd0, 0xc0, 0x98, 0x13, 0xa6, 0xbc, 0x5e, 0x34, 0x28, 0xa7, 0x30, 0x13, 0x8d, 0xb4, 0xca, 0x91, 0x26, 0x6c, 0xda, 0x35, 0xb5, 0xf1, 0xbf, 0x3f, 0x35, 0x3b, 0x87, 0x37, 0x63, 0x40, 0x59, 0x73, 0x49, 0x06, 0x59, 0x04, 0xe0, 0x84, 0x16, 0x3a, 0xe8, 0xc4, 0x28, 0xd1, 0xf5, 0x11, 0x9c, 0x34, 0xf4, 0x5a, 0xc0, 0xf8, 0x67, 0x47, 0x1c, 0x90, 0x63, 0xbc, 0x06, 0x39, 0x2e, 0x8a, 0xa5, 0xa0, 0xf1, 0x6b, 0x41, 0xb1, 0x16, 0xbd, 0xb9, 0x50, 0x78, 0x72, 0x91, 0x8e, 0x8c, 0x99, 0x0f, 0x7d, 0x99, 0x7e, 0x77, 0x36, 0x85, 0x87, 0x1f, 0x2e, 0x47, 0x13, 0x55, 0xf8, 0x07, 0xba, 0x7b, 0x1c, 0xaa, 0xbf, 0x20, 0xd0, 0xfa, 0xc4, 0xe1, 0xd0, 0xb3, 0xe4, 0xf4, 0xf9, 0x57, 0x8d, 0x56, 0x19, 0x4a, 0xdc, 0x4c, 0x83, 0xc8, 0xf1, 0x30, 0xc0, 0xb5, 0xdf, 0x67, 0x25, 0x58, 0xd8, 0x09, 0x41, 0x37, 0x2e, 0x0b, 0x47, 0x2b, 0x86, 0x4b, 0x73, 0x38, 0xf0, 0xa0, 0x6b, 0x83, 0x30, 0x80, 0x3e, 0x46, 0xb5, 0x09, 0xc8, 0x6d, 0x3e, 0x97, 0xaa, 0x70, 0x4e, 0x8c, 0x75, 0x29, 0xec, 0x8a, 0x37, 0x4a, 0x81, 0xfd, 0x92, 0xf1, 0x29, 0xf0, 0xe8, 0x9d, 0x8c, 0xb4, 0x39, 0x2d, 0x67, 0x06, 0xcd, 0x5f, 0x25, 0x02, 0x30, 0xbb, 0x6b, 0x41, 0x93, 0x55, 0x1e, 0x0c, 0xc9, 0x6e, 0xb5, 0xd5, 0x9f, 0x80, 0xf4, 0x7d, 0x9d, 0x8a, 0x0d, 0x8d, 0x3b, 0x15, 0x14, 0xc9, 0xdf, 0x03, 0x9c, 0x78, 0x39, 0x4e, 0xa0, 0xdc, 0x3a, 0x1b, 0x8c, 0xdf, 0xaa, 0xed, 0x25, 0xda, 0x60, 0xdd, 0x30, 0x64, 0x09, 0xcc, 0x94, 0x53, 0xa1, 0xad, 0xfd, 0x9e, 0xe7, 0x65, 0x15, 0xb8, 0xb1, 0xda, 0x9a, 0x28, 0x80, 0x51, 0x88, 0x93, 0x92, 0xe3, 0x03, 0xdf, 0x70, 0xba, 0x1b, 0x59, 0x3b, 0xb4, 0x8a, 0xb6, 0x0b, 0x0a, 0xa8, 0x48, 0xdf, 0xcc, 0x74, 0x4c, 0x71, 0x80, 0x08, 0xec, 0xc8, 0x8a, 0x73, 0xf5, 0x0e, 0x3d, 0xec, 0x16, 0xf6, 0x32, 0xfd, 0xf3, 0x6b, 0xba, 0xa9, 0x65, 0xd1, 0x87, 0xe2, 0x56, 0xcd, 0xde, 0x2c, 0xa4, 0x1b, 0x25, 0x81, 0xb2, 0xed, 0xea, 0xe9, 0x11, 0x07, 0xf5, 0x17, 0xd0, 0xca, 0x5d, 0x07, 0xb9, 0xb2, 0xa9, 0xa9, 0xee, 0x42, 0x33, 0x93, 0x21, 0x30, 0x5e, 0xd2, 0x58, 0xfd, 0xdd, 0x73, 0x0d, 0xb2, 0x93, 0x58, 0x77, 0x78, 0x40, 0x69, 0xba, 0x3c, 0x95, 0x1c, 0x61, 0xc6, 0xc6, 0x97, 0x1c, 0xef, 0x4d, 0x91, 0x0a, 0x42, 0x91, 0x1d, 0x14, 0x93, 0xf5, 0x78, 0x41, 0x32, 0x8a, 0x0a, 0x43, 0xd4, 0x3e, 0x6b, 0xb0, 0xd8, 0x0e, 0x04, } av1Pkt := &AV1Packet{} _, err := av1Pkt.Unmarshal(av1Payload) assert.NoError(t, err) expect := &AV1Packet{ Z: false, Y: true, W: 2, N: true, OBUElements: [][]byte{ av1Payload[2:14], av1Payload[14:], }, } assert.Equal(t, expect, av1Pkt, "AV1 Unmarshal didn't store the expected results in the packet") } func FuzzAV1PacketUnmarshal(f *testing.F) { f.Add([]byte{0x00, 0x00}) f.Add([]byte{0x10, 0x00}) f.Add([]byte{0x20, 0x00}) f.Add([]byte{0x30, 0x00}) f.Add([]byte{0x08, 0x00}) f.Add([]byte{0x80, 0x00}) f.Add([]byte{0x40, 0x00}) f.Add([]byte{0xC8, 0x00}) f.Add([]byte{0x00, 0x05, 0x01, 0x02, 0x03}) realPayload := []byte{ 0x68, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x2c, 0xd6, 0xd3, 0x0c, 0xd5, 0x02, 0x00, 0x80, } f.Add(realPayload) // look for crashes f.Fuzz(func(t *testing.T, data []byte) { pkt := &AV1Packet{} _, err := pkt.Unmarshal(data) _ = err }) } func FuzzAV1PayloaderPayloadMTU(f *testing.F) { sequenceHeader := (&obu.Header{ Type: obu.OBUSequenceHeader, HasSizeField: true, }).Marshal() f.Add(uint16(1500), append(sequenceHeader, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05)) frameHeader := (&obu.Header{ Type: obu.OBUFrameHeader, HasSizeField: true, }).Marshal() f.Add(uint16(500), append(frameHeader, 0x03, 0x01, 0x02, 0x03)) f.Add(uint16(1500), []byte{}) f.Add(uint16(10), append(frameHeader, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05)) f.Add(uint16(2), append(frameHeader, 0x03, 0x01, 0x02, 0x03)) largePayload := make([]byte, 0, 5000) largePayload = append(largePayload, frameHeader...) largePayload = append(largePayload, 0xFF, 0x26) for i := 0; i < 5000; i++ { largePayload = append(largePayload, byte(i%256)) } f.Add(uint16(1500), largePayload) f.Fuzz(func(t *testing.T, mtu uint16, data []byte) { if mtu == 0 { t.Skip() } payloader := &AV1Payloader{} payloads := payloader.Payload(mtu, data) for _, payload := range payloads { assert.LessOrEqual(t, len(payload), int(mtu), "Payload size should not exceed MTU") } }) } rtp-1.8.27/codecs/codecs.go000066400000000000000000000002741512154406600154540ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT // Package codecs implements codec specific RTP payloader/depayloaders package codecs rtp-1.8.27/codecs/common.go000066400000000000000000000020751512154406600155050ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs func minInt(a, b int) int { if a < b { return a } return b } // audioDepacketizer is a mixin for audio codec depacketizers. type audioDepacketizer struct{} func (d *audioDepacketizer) IsPartitionTail(_ bool, _ []byte) bool { return true } func (d *audioDepacketizer) IsPartitionHead(_ []byte) bool { return true } // videoDepacketizer is a mixin for video codec depacketizers. type videoDepacketizer struct { zeroAllocation bool } func (d *videoDepacketizer) IsPartitionTail(marker bool, _ []byte) bool { return marker } // SetZeroAllocation enables Zero Allocation mode for the depacketizer // By default the Depacketizers will allocate as they parse. These allocations // are needed for Metadata and other optional values. If you don't need this information // enabling SetZeroAllocation gives you higher performance at a reduced feature set. func (d *videoDepacketizer) SetZeroAllocation(zeroAllocation bool) { d.zeroAllocation = zeroAllocation } rtp-1.8.27/codecs/common_test.go000066400000000000000000000217411512154406600165450ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "testing" "github.com/stretchr/testify/assert" ) func TestCommon_Min(t *testing.T) { res := minInt(1, -1) assert.Equal(t, -1, res) res = minInt(1, 2) assert.Equal(t, 1, res) res = minInt(3, 3) assert.Equal(t, 3, res) } func TestZeroAllocations(t *testing.T) { //nolint:maintidx type unmarshaller interface { Unmarshal(data []byte) ([]byte, error) } type tst struct { packet unmarshaller data []byte } tests := []tst{ { packet: &VP8Packet{}, data: []byte{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x90, }, }, { packet: &VP9Packet{}, data: []byte{0xA0, 0x02, 0x23, 0x01, 0xAA}, }, { packet: &H264Packet{}, data: []byte{ 0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8, }, }, { packet: &AV1Packet{}, // nolint: dupl data: []byte{ 0x68, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x2c, 0xd6, 0xd3, 0x0c, 0xd5, 0x02, 0x00, 0x80, 0x30, 0x10, 0xc3, 0xc0, 0x07, 0xff, 0xff, 0xf8, 0xb7, 0x30, 0xc0, 0x00, 0x00, 0x88, 0x17, 0xf9, 0x0c, 0xcf, 0xc6, 0x7b, 0x9c, 0x0d, 0xda, 0x55, 0x82, 0x82, 0x67, 0x2f, 0xf0, 0x07, 0x26, 0x5d, 0xf6, 0xc6, 0xe3, 0x12, 0xdd, 0xf9, 0x71, 0x77, 0x43, 0xe6, 0xba, 0xf2, 0xce, 0x36, 0x08, 0x63, 0x92, 0xac, 0xbb, 0xbd, 0x26, 0x4c, 0x05, 0x52, 0x91, 0x09, 0xf5, 0x37, 0xb5, 0x18, 0xbe, 0x5c, 0x95, 0xb1, 0x2c, 0x13, 0x27, 0x81, 0xc2, 0x52, 0x8c, 0xaf, 0x27, 0xca, 0xf2, 0x93, 0xd6, 0x2e, 0x46, 0x32, 0xed, 0x71, 0x87, 0x90, 0x1d, 0x0b, 0x84, 0x46, 0x7f, 0xd1, 0x57, 0xc1, 0x0d, 0xc7, 0x5b, 0x41, 0xbb, 0x8a, 0x7d, 0xe9, 0x2c, 0xae, 0x36, 0x98, 0x13, 0x39, 0xb9, 0x0c, 0x66, 0x47, 0x05, 0xa2, 0xdf, 0x55, 0xc4, 0x09, 0xab, 0xe4, 0xfb, 0x11, 0x52, 0x36, 0x27, 0x88, 0x86, 0xf3, 0x4a, 0xbb, 0xef, 0x40, 0xa7, 0x85, 0x2a, 0xfe, 0x92, 0x28, 0xe4, 0xce, 0xce, 0xdc, 0x4b, 0xd0, 0xaa, 0x3c, 0xd5, 0x16, 0x76, 0x74, 0xe2, 0xfa, 0x34, 0x91, 0x4f, 0xdc, 0x2b, 0xea, 0xae, 0x71, 0x36, 0x74, 0xe1, 0x2a, 0xf3, 0xd3, 0x53, 0xe8, 0xec, 0xd6, 0x63, 0xf6, 0x6a, 0x75, 0x95, 0x68, 0xcc, 0x99, 0xbe, 0x17, 0xd8, 0x3b, 0x87, 0x5b, 0x94, 0xdc, 0xec, 0x32, 0x09, 0x18, 0x4b, 0x37, 0x58, 0xb5, 0x67, 0xfb, 0xdf, 0x66, 0x6c, 0x16, 0x9e, 0xba, 0x72, 0xc6, 0x21, 0xac, 0x02, 0x6d, 0x6b, 0x17, 0xf9, 0x68, 0x22, 0x2e, 0x10, 0xd7, 0xdf, 0xfb, 0x24, 0x69, 0x7c, 0xaf, 0x11, 0x64, 0x80, 0x7a, 0x9d, 0x09, 0xc4, 0x1f, 0xf1, 0xd7, 0x3c, 0x5a, 0xc2, 0x2c, 0x8e, 0xf5, 0xff, 0xee, 0xc2, 0x7c, 0xa1, 0xe4, 0xcb, 0x1c, 0x6d, 0xd8, 0x15, 0x0e, 0x40, 0x36, 0x85, 0xe7, 0x04, 0xbb, 0x64, 0xca, 0x6a, 0xd9, 0x21, 0x8e, 0x95, 0xa0, 0x83, 0x95, 0x10, 0x48, 0xfa, 0x00, 0x54, 0x90, 0xe9, 0x81, 0x86, 0xa0, 0x4a, 0x6e, 0xbe, 0x9b, 0xf0, 0x73, 0x0a, 0x17, 0xbb, 0x57, 0x81, 0x17, 0xaf, 0xd6, 0x70, 0x1f, 0xe8, 0x6d, 0x32, 0x59, 0x14, 0x39, 0xd8, 0x1d, 0xec, 0x59, 0xe4, 0x98, 0x4d, 0x44, 0xf3, 0x4f, 0x7b, 0x47, 0xd9, 0x92, 0x3b, 0xd9, 0x5c, 0x98, 0xd5, 0xf1, 0xc9, 0x8b, 0x9d, 0xb1, 0x65, 0xb3, 0xe1, 0x87, 0xa4, 0x6a, 0xcc, 0x42, 0x96, 0x66, 0xdb, 0x5f, 0xf9, 0xe1, 0xa1, 0x72, 0xb6, 0x05, 0x02, 0x1f, 0xa3, 0x14, 0x3e, 0xfe, 0x99, 0x7f, 0xeb, 0x42, 0xcf, 0x76, 0x09, 0x19, 0xd2, 0xd2, 0x99, 0x75, 0x1c, 0x67, 0xda, 0x4d, 0xf4, 0x87, 0xe5, 0x55, 0x8b, 0xed, 0x01, 0x82, 0xf6, 0xd6, 0x1c, 0x5c, 0x05, 0x96, 0x96, 0x79, 0xc1, 0x61, 0x87, 0x74, 0xcd, 0x29, 0x83, 0x27, 0xae, 0x47, 0x87, 0x36, 0x34, 0xab, 0xc4, 0x73, 0x76, 0x58, 0x1b, 0x4a, 0xec, 0x0e, 0x4c, 0x2f, 0xb1, 0x76, 0x08, 0x7f, 0xaf, 0xfa, 0x6d, 0x8c, 0xde, 0xe4, 0xae, 0x58, 0x87, 0xe7, 0xa0, 0x27, 0x05, 0x0d, 0xf5, 0xa7, 0xfb, 0x2a, 0x75, 0x33, 0xd9, 0x3b, 0x65, 0x60, 0xa4, 0x13, 0x27, 0xa5, 0xe5, 0x1b, 0x83, 0x78, 0x7a, 0xd7, 0xec, 0x0c, 0xed, 0x8b, 0xe6, 0x4e, 0x8f, 0xfe, 0x6b, 0x5d, 0xbb, 0xa8, 0xee, 0x38, 0x81, 0x6f, 0x09, 0x23, 0x08, 0x8f, 0x07, 0x21, 0x09, 0x39, 0xf0, 0xf8, 0x03, 0x17, 0x24, 0x2a, 0x22, 0x44, 0x84, 0xe1, 0x5c, 0xf3, 0x4f, 0x20, 0xdc, 0xc1, 0xe7, 0xeb, 0xbc, 0x0b, 0xfb, 0x7b, 0x20, 0x66, 0xa4, 0x27, 0xe2, 0x01, 0xb3, 0x5f, 0xb7, 0x47, 0xa1, 0x88, 0x4b, 0x8c, 0x47, 0xda, 0x36, 0x98, 0x60, 0xd7, 0x46, 0x92, 0x0b, 0x7e, 0x5b, 0x4e, 0x34, 0x50, 0x12, 0x67, 0x50, 0x8d, 0xe7, 0xc9, 0xe4, 0x96, 0xef, 0xae, 0x2b, 0xc7, 0xfa, 0x36, 0x29, 0x05, 0xf5, 0x92, 0xbd, 0x62, 0xb7, 0xbb, 0x90, 0x66, 0xe0, 0xad, 0x14, 0x3e, 0xe7, 0xb4, 0x24, 0xf3, 0x04, 0xcf, 0x22, 0x14, 0x86, 0xa4, 0xb8, 0xfb, 0x83, 0x56, 0xce, 0xaa, 0xb4, 0x87, 0x5a, 0x9e, 0xf2, 0x0b, 0xaf, 0xad, 0x40, 0xe1, 0xb5, 0x5c, 0x6b, 0xa7, 0xee, 0x9f, 0xbb, 0x1a, 0x68, 0x4d, 0xc3, 0xbf, 0x22, 0x4d, 0xbe, 0x58, 0x52, 0xc9, 0xcc, 0x0d, 0x88, 0x04, 0xf1, 0xf8, 0xd4, 0xfb, 0xd6, 0xad, 0xcf, 0x13, 0x84, 0xd6, 0x2f, 0x90, 0x0c, 0x5f, 0xb4, 0xe2, 0xd8, 0x29, 0x26, 0x8d, 0x7c, 0x6b, 0xab, 0x91, 0x91, 0x3c, 0x25, 0x39, 0x9c, 0x86, 0x08, 0x39, 0x54, 0x59, 0x0d, 0xa4, 0xa8, 0x31, 0x9f, 0xa3, 0xbc, 0xc2, 0xcb, 0xf9, 0x30, 0x49, 0xc3, 0x68, 0x0e, 0xfc, 0x2b, 0x9f, 0xce, 0x59, 0x02, 0xfa, 0xd4, 0x4e, 0x11, 0x49, 0x0d, 0x93, 0x0c, 0xae, 0x57, 0xd7, 0x74, 0xdd, 0x13, 0x1a, 0x15, 0x79, 0x10, 0xcc, 0x99, 0x32, 0x9b, 0x57, 0x6d, 0x53, 0x75, 0x1f, 0x6d, 0xbb, 0xe4, 0xbc, 0xa9, 0xd4, 0xdb, 0x06, 0xe7, 0x09, 0xb0, 0x6f, 0xca, 0xb3, 0xb1, 0xed, 0xc5, 0x0b, 0x8d, 0x8e, 0x70, 0xb0, 0xbf, 0x8b, 0xad, 0x2f, 0x29, 0x92, 0xdd, 0x5a, 0x19, 0x3d, 0xca, 0xca, 0xed, 0x05, 0x26, 0x25, 0xee, 0xee, 0xa9, 0xdd, 0xa0, 0xe3, 0x78, 0xe0, 0x56, 0x99, 0x2f, 0xa1, 0x3f, 0x07, 0x5e, 0x91, 0xfb, 0xc4, 0xb3, 0xac, 0xee, 0x07, 0xa4, 0x6a, 0xcb, 0x42, 0xae, 0xdf, 0x09, 0xe7, 0xd0, 0xbb, 0xc6, 0xd4, 0x38, 0x58, 0x7d, 0xb4, 0x45, 0x98, 0x38, 0x21, 0xc8, 0xc1, 0x3c, 0x81, 0x12, 0x7e, 0x37, 0x03, 0xa8, 0xcc, 0xf3, 0xf9, 0xd9, 0x9d, 0x8f, 0xc1, 0xa1, 0xcc, 0xc1, 0x1b, 0xe3, 0xa8, 0x93, 0x91, 0x2c, 0x0a, 0xe8, 0x1f, 0x28, 0x13, 0x44, 0x07, 0x68, 0x5a, 0x8f, 0x27, 0x41, 0x18, 0xc9, 0x31, 0xc4, 0xc1, 0x71, 0xe2, 0xf0, 0xc4, 0xf4, 0x1e, 0xac, 0x29, 0x49, 0x2f, 0xd0, 0xc0, 0x98, 0x13, 0xa6, 0xbc, 0x5e, 0x34, 0x28, 0xa7, 0x30, 0x13, 0x8d, 0xb4, 0xca, 0x91, 0x26, 0x6c, 0xda, 0x35, 0xb5, 0xf1, 0xbf, 0x3f, 0x35, 0x3b, 0x87, 0x37, 0x63, 0x40, 0x59, 0x73, 0x49, 0x06, 0x59, 0x04, 0xe0, 0x84, 0x16, 0x3a, 0xe8, 0xc4, 0x28, 0xd1, 0xf5, 0x11, 0x9c, 0x34, 0xf4, 0x5a, 0xc0, 0xf8, 0x67, 0x47, 0x1c, 0x90, 0x63, 0xbc, 0x06, 0x39, 0x2e, 0x8a, 0xa5, 0xa0, 0xf1, 0x6b, 0x41, 0xb1, 0x16, 0xbd, 0xb9, 0x50, 0x78, 0x72, 0x91, 0x8e, 0x8c, 0x99, 0x0f, 0x7d, 0x99, 0x7e, 0x77, 0x36, 0x85, 0x87, 0x1f, 0x2e, 0x47, 0x13, 0x55, 0xf8, 0x07, 0xba, 0x7b, 0x1c, 0xaa, 0xbf, 0x20, 0xd0, 0xfa, 0xc4, 0xe1, 0xd0, 0xb3, 0xe4, 0xf4, 0xf9, 0x57, 0x8d, 0x56, 0x19, 0x4a, 0xdc, 0x4c, 0x83, 0xc8, 0xf1, 0x30, 0xc0, 0xb5, 0xdf, 0x67, 0x25, 0x58, 0xd8, 0x09, 0x41, 0x37, 0x2e, 0x0b, 0x47, 0x2b, 0x86, 0x4b, 0x73, 0x38, 0xf0, 0xa0, 0x6b, 0x83, 0x30, 0x80, 0x3e, 0x46, 0xb5, 0x09, 0xc8, 0x6d, 0x3e, 0x97, 0xaa, 0x70, 0x4e, 0x8c, 0x75, 0x29, 0xec, 0x8a, 0x37, 0x4a, 0x81, 0xfd, 0x92, 0xf1, 0x29, 0xf0, 0xe8, 0x9d, 0x8c, 0xb4, 0x39, 0x2d, 0x67, 0x06, 0xcd, 0x5f, 0x25, 0x02, 0x30, 0xbb, 0x6b, 0x41, 0x93, 0x55, 0x1e, 0x0c, 0xc9, 0x6e, 0xb5, 0xd5, 0x9f, 0x80, 0xf4, 0x7d, 0x9d, 0x8a, 0x0d, 0x8d, 0x3b, 0x15, 0x14, 0xc9, 0xdf, 0x03, 0x9c, 0x78, 0x39, 0x4e, 0xa0, 0xdc, 0x3a, 0x1b, 0x8c, 0xdf, 0xaa, 0xed, 0x25, 0xda, 0x60, 0xdd, 0x30, 0x64, 0x09, 0xcc, 0x94, 0x53, 0xa1, 0xad, 0xfd, 0x9e, 0xe7, 0x65, 0x15, 0xb8, 0xb1, 0xda, 0x9a, 0x28, 0x80, 0x51, 0x88, 0x93, 0x92, 0xe3, 0x03, 0xdf, 0x70, 0xba, 0x1b, 0x59, 0x3b, 0xb4, 0x8a, 0xb6, 0x0b, 0x0a, 0xa8, 0x48, 0xdf, 0xcc, 0x74, 0x4c, 0x71, 0x80, 0x08, 0xec, 0xc8, 0x8a, 0x73, 0xf5, 0x0e, 0x3d, 0xec, 0x16, 0xf6, 0x32, 0xfd, 0xf3, 0x6b, 0xba, 0xa9, 0x65, 0xd1, 0x87, 0xe2, 0x56, 0xcd, 0xde, 0x2c, 0xa4, 0x1b, 0x25, 0x81, 0xb2, 0xed, 0xea, 0xe9, 0x11, 0x07, 0xf5, 0x17, 0xd0, 0xca, 0x5d, 0x07, 0xb9, 0xb2, 0xa9, 0xa9, 0xee, 0x42, 0x33, 0x93, 0x21, 0x30, 0x5e, 0xd2, 0x58, 0xfd, 0xdd, 0x73, 0x0d, 0xb2, 0x93, 0x58, 0x77, 0x78, 0x40, 0x69, 0xba, 0x3c, 0x95, 0x1c, 0x61, 0xc6, 0xc6, 0x97, 0x1c, 0xef, 0x4d, 0x91, 0x0a, 0x42, 0x91, 0x1d, 0x14, 0x93, 0xf5, 0x78, 0x41, 0x32, 0x8a, 0x0a, 0x43, 0xd4, 0x3e, 0x6b, 0xb0, 0xd8, 0x0e, 0x04, }, }, } type zeroAllocation interface { SetZeroAllocation(zeroAllocation bool) } for _, test := range tests { allocs := testing.AllocsPerRun(10, func() { if d, ok := test.packet.(zeroAllocation); ok { d.SetZeroAllocation(true) } _, err := test.packet.Unmarshal(test.data) assert.NoError(t, err) }) assert.Equal(t, 0.0, allocs) } } rtp-1.8.27/codecs/error.go000066400000000000000000000011311512154406600153360ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import "errors" var ( errShortPacket = errors.New("packet is not large enough") errNilPacket = errors.New("invalid nil packet") errTooManyPDiff = errors.New("too many PDiff") errTooManySpatialLayers = errors.New("too many spatial layers") errUnhandledNALUType = errors.New("NALU Type is unhandled") // AV1 Errors. errIsKeyframeAndFragment = errors.New( "bits Z and N are set. Not possible to have OBU be tail fragment and be keyframe", ) ) rtp-1.8.27/codecs/g711_packet.go000066400000000000000000000011341512154406600162160ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs // G711Payloader payloads G711 packets. type G711Payloader struct{} // Payload fragments an G711 packet across one or more byte arrays. func (p *G711Payloader) Payload(mtu uint16, payload []byte) [][]byte { var out [][]byte if payload == nil || mtu == 0 { return out } for len(payload) > int(mtu) { o := make([]byte, mtu) copy(o, payload[:mtu]) payload = payload[mtu:] out = append(out, o) } o := make([]byte, len(payload)) copy(o, payload) return append(out, o) } rtp-1.8.27/codecs/g711_packet_test.go000066400000000000000000000030711512154406600172570ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs //nolint:dupl import ( "bytes" "crypto/rand" "math" "testing" "github.com/stretchr/testify/assert" ) func TestG711Payloader(t *testing.T) { payloader := G711Payloader{} const ( testlen = 10000 testmtu = 1500 ) // generate random 8-bit g722 samples samples := make([]byte, testlen) _, err := rand.Read(samples) assert.NoError(t, err) // make a copy, for payloader input samplesIn := make([]byte, testlen) copy(samplesIn, samples) // split our samples into payloads payloads := payloader.Payload(testmtu, samplesIn) outcnt := int(math.Ceil(float64(testlen) / testmtu)) assert.Len(t, payloads, outcnt) assert.Equal(t, samplesIn, samples, "Modified input samples") samplesOut := bytes.Join(payloads, []byte{}) assert.Equal(t, samplesIn, samplesOut) payload := []byte{0x90, 0x90, 0x90} // 0 MTU, small payload res := payloader.Payload(0, payload) assert.Len(t, res, 0, "Generated payload should be empty") // Positive MTU, small payload res = payloader.Payload(1, payload) assert.Len(t, res, len(payload), "Generated payload should be the same size as original payload size") // Positive MTU, small payload res = payloader.Payload(uint16(len(payload)-1), payload) // nolint: gosec // G115 assert.Len(t, res, len(payload)-1, "Generated payload should be the same smaller than original payload size") // Positive MTU, small payload res = payloader.Payload(10, payload) assert.Len(t, res, 1, "Generated payload should be the 1") } rtp-1.8.27/codecs/g722_packet.go000066400000000000000000000011341512154406600162200ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs // G722Payloader payloads G722 packets. type G722Payloader struct{} // Payload fragments an G722 packet across one or more byte arrays. func (p *G722Payloader) Payload(mtu uint16, payload []byte) [][]byte { var out [][]byte if payload == nil || mtu == 0 { return out } for len(payload) > int(mtu) { o := make([]byte, mtu) copy(o, payload[:mtu]) payload = payload[mtu:] out = append(out, o) } o := make([]byte, len(payload)) copy(o, payload) return append(out, o) } rtp-1.8.27/codecs/g722_packet_test.go000066400000000000000000000031271512154406600172630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs //nolint:dupl import ( "bytes" "crypto/rand" "math" "testing" "github.com/stretchr/testify/assert" ) func TestG722Payloader(t *testing.T) { payloader := G722Payloader{} const ( testlen = 10000 testmtu = 1500 ) // generate random 8-bit g722 samples samples := make([]byte, testlen) _, err := rand.Read(samples) assert.NoError(t, err) // make a copy, for payloader input samplesIn := make([]byte, testlen) copy(samplesIn, samples) // split our samples into payloads payloads := payloader.Payload(testmtu, samplesIn) outcnt := int(math.Ceil(float64(testlen) / testmtu)) assert.Len(t, payloads, outcnt) assert.Equal(t, samplesIn, samples, "Modified input samples") samplesOut := bytes.Join(payloads, []byte{}) assert.Equal(t, samplesIn, samplesOut, "Output samples don't match") payload := []byte{0x90, 0x90, 0x90} // 0 MTU, small payload res := payloader.Payload(0, payload) assert.Len(t, res, 0, "Generated payload should be empty") // Positive MTU, small payload res = payloader.Payload(1, payload) assert.Len(t, res, len(payload), "Generated payload should be the same size as original payload size") // Positive MTU, small payload res = payloader.Payload(uint16(len(payload)-1), payload) // nolint: gosec // G115 assert.Len(t, res, len(payload)-1, "Generated payload should be the same smaller than original payload size") // Positive MTU, small payload res = payloader.Payload(10, payload) assert.Len(t, res, 1, "Generated payload should be the 1") } rtp-1.8.27/codecs/h264/000077500000000000000000000000001512154406600143455ustar00rootroot00000000000000rtp-1.8.27/codecs/h264/spreader.go000066400000000000000000000350021512154406600165010ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT // Package h264 provides helpers for working with H264 Bitstreams package h264 import ( "encoding/binary" "fmt" "github.com/pion/rtp" ) type Spreader struct { Mtu int Spreading bool RTPOffset uint16 fuInProgress *fuInProgress trailingBuf []byte } type fuInProgress struct { LastSeq uint16 RTPHeader []byte Trailing []byte FuStartBytes [2]byte } const ( minRTPHeaderSize = 12 rtpVPECsrcOffset = 0 rtpMPtOffset = 1 rtpSeqNumOffset = 2 rtpSeqNumLength = 2 nalUnitTypeOffset = 0 nalUnitTypeSize = 1 fuaOverhead = 2 fuaIndicatorOffset = 0 fuaHeaderOffest = 1 fubNALUType = 29 fuaNALUType = 28 stapbNALUType = 25 mtap16NALUType = 26 mtap24NALUType = 27 stapaNALUType = 24 stapaHeaderSize = 1 stapaNALULengthSize = 2 fuEndBitmask = byte(0x40) naluTypeBitmask = byte(0x1F) rtpPaddingBitMask = byte(0x20) rtpMarkerBitMask = byte(0x80) fuStartBitmask = byte(0x80) ) // From rfc3550 // =================================== // RTP header (minimal part) // =================================== // // 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|X| CC |M| PT | sequence number | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | timestamp | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | synchronization source (SSRC) identifier | // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // // // From rfc6184 // =================================== // Single NAL Unit Packet // =================================== // // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // |F|NRI| Type | | // +-+-+-+-+-+-+-+-+ | // | | // | Bytes 2..n of a single NAL unit | // | | // | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | :...OPTIONAL RTP padding | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // =================================== // FU-A // =================================== // // RTP payload format for FU-A : // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | FU indicator | FU header | | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | // | FU payload | // | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | :...OPTIONAL RTP padding | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // The FU indicator octet has the following format: // +---------------+ // |0|1|2|3|4|5|6|7| // +-+-+-+-+-+-+-+-+ // |F|NRI| Type | // +---------------+ // // The FU header has the following format: // +---------------+ // |0|1|2|3|4|5|6|7| // +-+-+-+-+-+-+-+-+ // |S|E|R| Type | // +---------------+ // // =================================== // STAP-A // =================================== // // An example of an RTP packet including an STAP-A containing two single-time aggregation units // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | RTP Header | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // |STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | NALU 1 Data | // : : // + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | | NALU 2 Size | NALU 2 HDR | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | NALU 2 Data | // : : // | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | :...OPTIONAL RTP padding | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ func NewSpreader(mtu uint16) Spreader { return Spreader{ Mtu: int(mtu), Spreading: false, RTPOffset: 0, fuInProgress: nil, trailingBuf: make([]byte, mtu), } } func (s *Spreader) Process(payload []byte) (outPayloads [][]byte, err error) { // nolint: cyclop outPayloads = make([][]byte, 0, 4) payLen := len(payload) //nolint:gocritic // keep the chain to highlight the decision order if payLen == 0 { return outPayloads, nil } else if payLen < minRTPHeaderSize { return nil, fmt.Errorf("payload is too small: %d", payLen) //nolint:err113 } else if !s.Spreading && (payLen <= s.Mtu) { // best case scenario : all RTP pkts were small enough up to now, nothing to do! Pkt goes straight! outPayloads = append(outPayloads, payload) return outPayloads, nil } s.Spreading = true // rtp seq offset to compensate for the previous extra pkts we inserted seqNum := binary.BigEndian.Uint16(payload[rtpSeqNumOffset : rtpSeqNumOffset+rtpSeqNumLength]) seqNum += s.RTPOffset binary.BigEndian.PutUint16(payload[rtpSeqNumOffset:rtpSeqNumOffset+rtpSeqNumLength], seqNum) if s.fuInProgress == nil && (payLen <= s.Mtu) { // whenever possible, forward RTP pkts without any Unmarshal() outPayloads = append(outPayloads, payload) return outPayloads, nil } rtpPkt := &rtp.Packet{} err = rtpPkt.Unmarshal(payload) if err != nil { return nil, err } else if len(rtpPkt.Payload) < 2 { return nil, fmt.Errorf("nal content is too small: %d", len(rtpPkt.Payload)) //nolint:err113 } // avoiding repetitive RTP Marshal() by passing around the RTP header slice (as a data template) nalData := rtpPkt.Payload rtpHeaderSize := payLen - len(rtpPkt.Payload) - int(rtpPkt.PaddingSize) rtpHeaderData := payload[:rtpHeaderSize] rtpHeaderData[rtpVPECsrcOffset] &= ^rtpPaddingBitMask naluType := nalData[nalUnitTypeOffset] & naluTypeBitmask if naluType != fuaNALUType && s.fuInProgress != nil { outPayloads, seqNum = s.flushFuPending(outPayloads, seqNum) if payLen <= s.Mtu { outPayloads = append(outPayloads, payload) s.RTPOffset += uint16(len(outPayloads) - 1) //nolint:gosec return outPayloads, nil } } outPayloads, _, err = s.handleNalTooBigOrFua(outPayloads, seqNum, naluType, rtpHeaderData, nalData) if err != nil { return nil, err } s.RTPOffset += uint16(len(outPayloads) - 1) //nolint:gosec return outPayloads, nil } func (s *Spreader) handleNalTooBigOrFua(cumulRTP [][]byte, seqNum uint16, naluType byte, rtpHeader []byte, nalData []byte) ([][]byte, uint16, error) { //nolint:lll switch naluType { case stapaNALUType: return s.explodeStapA(cumulRTP, seqNum, rtpHeader, nalData) case fuaNALUType: return s.spreadFua(cumulRTP, seqNum, rtpHeader, nalData) case stapbNALUType, mtap16NALUType, mtap24NALUType, fubNALUType: return nil, seqNum, fmt.Errorf("DON or MTAP are not supported") //nolint:err113 default: return s.spreadSingleNalToFua(cumulRTP, seqNum, rtpHeader, nalData) } } // relying on continuous seq number & start/end FU bits to sync ourselve, so not looking at RtpTimestamp. func (s *Spreader) spreadFua(cumulRTP [][]byte, firtSeqNum uint16, rtpHeader []byte, fua []byte) ([][]byte, uint16, error) { //nolint:lll seqNum := firtSeqNum if s.fuInProgress != nil { expectedSeq := s.fuInProgress.LastSeq + 1 if firtSeqNum != expectedSeq { cumulRTP, seqNum = s.flushFuPending(cumulRTP, seqNum) // restart over clean (recurse) return s.spreadFua(cumulRTP, seqNum, rtpHeader, fua) } } entryMarker := rtpHeader[rtpMPtOffset] & rtpMarkerBitMask rtpHeader[rtpMPtOffset] &= ^rtpMarkerBitMask lenRTPHeader := len(rtpHeader) if s.fuInProgress == nil { rtpHeaderCpy := make([]byte, lenRTPHeader) copy(rtpHeaderCpy, rtpHeader) s.fuInProgress = &fuInProgress{ LastSeq: seqNum, RTPHeader: rtpHeaderCpy, Trailing: nil, } s.fuInProgress.FuStartBytes[fuaIndicatorOffset] = fua[fuaIndicatorOffset] s.fuInProgress.FuStartBytes[fuaHeaderOffest] = fua[fuaHeaderOffest] & (^fuEndBitmask) } var lastFuHeader *byte mustFinish := (fua[fuaHeaderOffest] & fuEndBitmask) != 0 reqSubSize := s.Mtu - lenRTPHeader - fuaOverhead newData := fua[fuaOverhead:] currentDataSize := len(s.fuInProgress.Trailing) + len(newData) for currentDataSize > reqSubSize || (mustFinish && currentDataSize > 0) { bufSize := min(s.Mtu, lenRTPHeader+fuaOverhead+currentDataSize) rtp := make([]byte, bufSize) binary.BigEndian.PutUint16(rtpHeader[rtpSeqNumOffset:rtpSeqNumOffset+rtpSeqNumLength], seqNum) copy(rtp, rtpHeader) copy(rtp[lenRTPHeader:], s.fuInProgress.FuStartBytes[:]) lastFuHeader = &rtp[lenRTPHeader+1] lenTrailing := len(s.fuInProgress.Trailing) if lenTrailing > 0 { copy(rtp[lenRTPHeader+fuaOverhead:], s.fuInProgress.Trailing) s.fuInProgress.Trailing = nil } toCopyFromNew := min(reqSubSize-lenTrailing, len(newData)) if toCopyFromNew > 0 { copy(rtp[lenRTPHeader+fuaOverhead+lenTrailing:], newData[:toCopyFromNew]) newData = newData[toCopyFromNew:] } cumulRTP = append(cumulRTP, rtp) s.fuInProgress.FuStartBytes[fuaHeaderOffest] &= ^fuStartBitmask s.fuInProgress.LastSeq = seqNum seqNum += 1 currentDataSize = len(newData) } if mustFinish { *lastFuHeader |= fuEndBitmask s.fuInProgress = nil } else { copy(s.trailingBuf, newData) s.fuInProgress.Trailing = s.trailingBuf[:len(newData)] } cumulRTP[len(cumulRTP)-1][rtpMPtOffset] |= entryMarker return cumulRTP, seqNum, nil } func (s *Spreader) flushFuPending(cumulRTP [][]byte, entrySeq uint16) ([][]byte, uint16) { seqNum := entrySeq fuInProgress := s.fuInProgress s.fuInProgress = nil if fuInProgress != nil && len(fuInProgress.Trailing) > 0 { lenPrevRTPHeader := len(fuInProgress.RTPHeader) rtp := make([]byte, lenPrevRTPHeader+fuaOverhead+len(fuInProgress.Trailing)) newSeq := fuInProgress.LastSeq + 1 binary.BigEndian.PutUint16(fuInProgress.RTPHeader[rtpSeqNumOffset:rtpSeqNumOffset+rtpSeqNumLength], newSeq) // can't have trailing if was 'ending' before //nolint:lll fuInProgress.FuStartBytes[fuaHeaderOffest] &= ^(fuStartBitmask | fuEndBitmask) copy(rtp, fuInProgress.RTPHeader) copy(rtp[lenPrevRTPHeader:], fuInProgress.FuStartBytes[:]) copy(rtp[lenPrevRTPHeader+fuaOverhead:], fuInProgress.Trailing) seqNum += 1 return append(cumulRTP, rtp), seqNum } return cumulRTP, seqNum } func (s *Spreader) spreadSingleNalToFua(cumulRTP [][]byte, firtSeqNum uint16, rtpHeader []byte, nal []byte) ([][]byte, uint16, error) { //nolint:lll entryMarker := rtpHeader[rtpMPtOffset] & rtpMarkerBitMask rtpHeader[rtpMPtOffset] &= ^rtpMarkerBitMask naluType := nal[nalUnitTypeOffset] & naluTypeBitmask fuHeader := naluType | fuStartBitmask fuIndicator := (nal[nalUnitTypeOffset] ^ naluTypeBitmask) | fuaNALUType lenRTPHeader := len(rtpHeader) reqSubSize := s.Mtu - lenRTPHeader - fuaOverhead // rfc6184: // The NAL unit type octet of the fragmented NAL unit is not included as such in the fragmentation unit payload, // but rather the information of the NAL unit type octet of the fragmented NAL unit is conveyed in the F and NRI // fields of the FU indicator octet of the fragmentation unit and in the type field of the FU header. nalWithoutHeader := nal[nalUnitTypeSize:] chunks := sliceTo(reqSubSize, nalWithoutHeader) nbChunks := len(chunks) buf := make([]byte, len(nalWithoutHeader)+((fuaOverhead+lenRTPHeader)*nbChunks)) offset := 0 seqNum := firtSeqNum var lastFuHeader *byte for _, chunk := range chunks { cumulRTP = append(cumulRTP, buf[offset:offset+lenRTPHeader+fuaOverhead+len(chunk)]) binary.BigEndian.PutUint16(rtpHeader[rtpSeqNumOffset:rtpSeqNumOffset+rtpSeqNumLength], seqNum) copy(buf[offset:], rtpHeader) offset += lenRTPHeader buf[offset] = fuIndicator offset += 1 buf[offset] = fuHeader lastFuHeader = &buf[offset] offset += 1 copy(buf[offset:], chunk) offset += len(chunk) seqNum += 1 fuHeader &= ^fuStartBitmask } *lastFuHeader |= fuEndBitmask cumulRTP[len(cumulRTP)-1][rtpMPtOffset] |= entryMarker return cumulRTP, seqNum, nil } //nolint:lll func (s *Spreader) explodeStapA( cumulRTP [][]byte, firtSeqNum uint16, rtpHeader []byte, stapa []byte, ) ([][]byte, uint16, error) { entryMarker := rtpHeader[rtpMPtOffset] & rtpMarkerBitMask rtpHeader[rtpMPtOffset] &= ^rtpMarkerBitMask lenRTPHeader := len(rtpHeader) maxSize := s.Mtu - lenRTPHeader currOffset := int(stapaHeaderSize) lenStapA := len(stapa) seqNum := firtSeqNum var err error for currOffset < lenStapA { naluSize := int(binary.BigEndian.Uint16(stapa[currOffset:])) currOffset += stapaNALULengthSize if lenStapA < currOffset+naluSize { return nil, seqNum, fmt.Errorf("STAP-A declared size(%d) is larger than buffer(%d)", naluSize, lenStapA-currOffset) //nolint:err113 } subNal := stapa[currOffset : currOffset+naluSize] currOffset += naluSize if naluSize <= maxSize { rtp := make([]byte, lenRTPHeader+naluSize) binary.BigEndian.PutUint16(rtpHeader[rtpSeqNumOffset:rtpSeqNumOffset+rtpSeqNumLength], seqNum) copy(rtp, rtpHeader) copy(rtp[lenRTPHeader:], subNal) cumulRTP = append(cumulRTP, rtp) seqNum += 1 } else { cumulRTP, seqNum, err = s.spreadSingleNalToFua(cumulRTP, seqNum, rtpHeader, subNal) if err != nil { return nil, seqNum, err } } } cumulRTP[len(cumulRTP)-1][rtpMPtOffset] |= entryMarker return cumulRTP, seqNum, nil } func sliceTo(reqSize int, data []byte) [][]byte { chunkNb := (len(data) + reqSize - 1) / reqSize chunks := make([][]byte, chunkNb) for i := 0; i < (chunkNb - 1); i++ { rangeStart := i * reqSize chunks[i] = data[rangeStart : rangeStart+reqSize] } chunks[chunkNb-1] = data[(chunkNb-1)*reqSize:] return chunks } rtp-1.8.27/codecs/h264/spreader_test.go000066400000000000000000003452741512154406600175570ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package h264 import ( "encoding/binary" "testing" "github.com/stretchr/testify/assert" ) func TestPassthrough(t *testing.T) { assert := assert.New(t) // arrange spreader := NewSpreader(1200) rtpPkt := append(getRTPHeader(false), getH264StapADelimiterAndSpsAndPps()...) // act out, err := spreader.Process(rtpPkt) // assert if assert.NoError(err, "failed to process pkt") { assert.Equal(false, spreader.Spreading, "should not be spreading") assert.Equal(uint16(0), spreader.RTPOffset, "Bad offset") assert.Equal(1, len(out), "bad pkt out count") assert.Equal(rtpPkt, out[0], "bad payload") } } func TestSmallPktsAreOffsetedOnceSpreadingStart(t *testing.T) { assert := assert.New(t) // arrange spreader := NewSpreader(1200) spreader.Spreading = true spreader.RTPOffset = 25 rtpPkt := append(getRTPHeader(false), getH264StapADelimiterAndSpsAndPps()...) // act out, err := spreader.Process(rtpPkt) // assert if assert.NoError(err, "failed to process pkt") { assert.Equal(true, spreader.Spreading, "should be spreading") assert.Equal(uint16(25), spreader.RTPOffset, "Bad offset") assert.Equal(1, len(out), "bad pkt out count") assert.Equal(append(getRTPHeaderOffSet(uint16(25), false), getH264StapADelimiterAndSpsAndPps()...), out[0], "bad payload") // nolint: lll } } func TestExplodingStap(t *testing.T) { assert := assert.New(t) // arrange spreader := NewSpreader(26) rtpPkt := append(getRTPHeader(false), getH264StapADelimiterAndSpsAndPps()...) // act out, err := spreader.Process(rtpPkt) // assert if assert.NoError(err, "failed to process pkt") { assert.Equal(true, spreader.Spreading, "should be spreading") assert.Equal(uint16(3), spreader.RTPOffset, "Bad offset") SpsFuaStart := []byte{0x7C, 0x87, 0x4D, 0x40, 0x2A, 0xEC, 0xA0, 0x3C, 0x01, 0x13, 0xF2, 0xC2, 0x00, 0x00} SpsFuaEnd := []byte{0x7C, 0x47, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0xF1, 0x1E, 0x30, 0x63, 0x2C} assert.Equal(4, len(out), "bad pkt out count: one delimiter, SPS split in two fua, one PPS") assert.Equal(append(getRTPHeaderOffSet(0, false), getH264DelimiterNal()...), out[0], "bad payload delimiter") assert.Equal(append(getRTPHeaderOffSet(1, false), SpsFuaStart...), out[1], "bad payload SPS start FUA") assert.Equal(append(getRTPHeaderOffSet(2, false), SpsFuaEnd...), out[2], "bad payload SPS end FUA") assert.Equal(append(getRTPHeaderOffSet(3, false), getH264PpsNal()...), out[3], "bad payload") } } func TestExplodingStapWithMarker(t *testing.T) { assert := assert.New(t) // arrange spreader := NewSpreader(39) rtpPkt := append(getRTPHeader(true), getH264StapADelimiterAndSpsAndPps()...) // act out, err := spreader.Process(rtpPkt) // assert if assert.NoError(err, "failed to process pkt") { assert.Equal(true, spreader.Spreading, "should be spreading") assert.Equal(uint16(2), spreader.RTPOffset, "Bad offset") assert.Equal(3, len(out), "bad pkt out count: one delimiter, SPS split in two fua, one PPS") assert.Equal(append(getRTPHeaderOffSet(0, false), getH264DelimiterNal()...), out[0], "bad payload delimiter") assert.Equal(append(getRTPHeaderOffSet(1, false), getH264SpsNal()...), out[1], "bad payload SPS start FUA") assert.Equal(append(getRTPHeaderOffSet(2, true), getH264PpsNal()...), out[2], "bad payload") } } func TestSplitSingleNalInFua(t *testing.T) { assert := assert.New(t) // arrange spreader := NewSpreader(20) rtpPkt := append(getRTPHeader(true), getH264SpsNal()...) // act out, err := spreader.Process(rtpPkt) // assert if assert.NoError(err, "failed to process pkt") { assert.Equal(true, spreader.Spreading, "should be spreading") assert.Equal(uint16(3), spreader.RTPOffset, "Bad offset") SpsFuaStart := []byte{0x7C, 0x87, 0x4D, 0x40, 0x2A, 0xEC, 0xA0, 0x3C} SpsFuaMiddle1 := []byte{0x7C, 0x07, 0x01, 0x13, 0xF2, 0xC2, 0x00, 0x00} SpsFuaMiddle2 := []byte{0x7C, 0x07, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03} SpsFuaEnd := []byte{0x7C, 0x47, 0x00, 0xF1, 0x1E, 0x30, 0x63, 0x2C} assert.Equal(4, len(out), "bad pkt out count: one delimiter, SPS split in two fua, one PPS") assert.Equal(append(getRTPHeaderOffSet(0, false), SpsFuaStart...), out[0], "bad payload SPS start FUA") assert.Equal(append(getRTPHeaderOffSet(1, false), SpsFuaMiddle1...), out[1], "bad payload SPS middle1 FUA") assert.Equal(append(getRTPHeaderOffSet(2, false), SpsFuaMiddle2...), out[2], "bad payload SPS middle2 FUA") assert.Equal(append(getRTPHeaderOffSet(3, true), SpsFuaEnd...), out[3], "bad payload SPS end FUA") } } func TestFuaAreSpreadMaxingMtuAcrossPkts(t *testing.T) { assert := assert.New(t) // arrange EntrySpsFuaStart := []byte{0x7C, 0x87, 0x4D, 0x40, 0x2A, 0xEC, 0xA0, 0x3C, 0x01, 0x13, 0xF2, 0xC2, 0x00, 0x00} EntrySpsFuaEnd := []byte{0x7C, 0x47, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0xF1, 0x1E, 0x30, 0x63, 0x2C} spreader := NewSpreader(24) fua1 := append(getRTPHeaderOffSet(0, false), EntrySpsFuaStart...) fua2 := append(getRTPHeaderOffSet(1, false), EntrySpsFuaEnd...) // act out1, err1 := spreader.Process(fua1) out2, err2 := spreader.Process(fua2) out := append(out1, out2...) // nolint: gocritic // assert assert.NoError(err1, "failed to process pkt") assert.NoError(err2, "failed to process pkt") SpsFuaStart := []byte{0x7C, 0x87, 0x4D, 0x40, 0x2A, 0xEC, 0xA0, 0x3C, 0x01, 0x13, 0xF2, 0xC2} SpsFuaMiddle := []byte{0x7C, 0x07, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0xF1} SpsFuaEnd := []byte{0x7C, 0x47, 0x1E, 0x30, 0x63, 0x2C} assert.Equal(true, spreader.Spreading, "should be spreading") assert.Equal(uint16(1), spreader.RTPOffset, "Bad offset") assert.Equal(3, len(out), "bad pkt out count: one delimiter, SPS split in two fua, one PPS") assert.Equal(append(getRTPHeaderOffSet(0, false), SpsFuaStart...), out[0], "bad payload SPS start FUA") assert.Equal(append(getRTPHeaderOffSet(1, false), SpsFuaMiddle...), out[1], "bad payload SPS middle FUA") assert.Equal(append(getRTPHeaderOffSet(2, false), SpsFuaEnd...), out[2], "bad payload SPS end FUA") } func TestFuaRealFromSameSrcBuffer(t *testing.T) { assert := assert.New(t) // arrange spreader := NewSpreader(1200) refRTPFuas := getRtpsFuaNonIdr() var refFuaConcat []byte for _, slice := range refRTPFuas { refFuaConcat = append(refFuaConcat, slice[14:]...) } // act rtpFuasToFeed := getRtpsFuaNonIdr() var rtpOutsConcat []byte alwaysSameBuf := make([]byte, 3000) for _, rtp := range rtpFuasToFeed { copy(alwaysSameBuf, rtp) out, err := spreader.Process(alwaysSameBuf[:len(rtp)]) assert.NoError(err, "failed to process pkt") for _, singleRTP := range out { rtpOutsConcat = append(rtpOutsConcat, singleRTP[14:]...) } } // assert assert.Equal(len(refFuaConcat), len(rtpOutsConcat), "bad reassambly length") assert.Equal(refFuaConcat, rtpOutsConcat, "bad reassambly") } func TestChunkSlicing(t *testing.T) { assert := assert.New(t) twelve := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} slice12to20 := sliceTo(20, twelve) assert.Equal(1, len(slice12to20), "Bad Slicing") assert.Equal(slice12to20[0], []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, "Bad Slicing") slice12to12 := sliceTo(12, twelve) assert.Equal(1, len(slice12to12), "Bad Slicing") assert.Equal(slice12to12[0], []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, "Bad Slicing") slice12to4 := sliceTo(4, twelve) assert.Equal(3, len(slice12to4), "Bad Slicing") assert.Equal(slice12to4[0], []byte{1, 2, 3, 4}, "Bad Slicing") assert.Equal(slice12to4[1], []byte{5, 6, 7, 8}, "Bad Slicing") assert.Equal(slice12to4[2], []byte{9, 10, 11, 12}, "Bad Slicing") thirteen := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13} slice13to4 := sliceTo(4, thirteen) assert.Equal(4, len(slice13to4), "Bad Slicing") assert.Equal(slice13to4[0], []byte{1, 2, 3, 4}, "Bad Slicing") assert.Equal(slice13to4[1], []byte{5, 6, 7, 8}, "Bad Slicing") assert.Equal(slice13to4[2], []byte{9, 10, 11, 12}, "Bad Slicing") assert.Equal(slice13to4[3], []byte{13}, "Bad Slicing") fourteen := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} slice14to4 := sliceTo(4, fourteen) assert.Equal(4, len(slice14to4), "Bad Slicing") assert.Equal(slice14to4[0], []byte{1, 2, 3, 4}, "Bad Slicing") assert.Equal(slice14to4[1], []byte{5, 6, 7, 8}, "Bad Slicing") assert.Equal(slice14to4[2], []byte{9, 10, 11, 12}, "Bad Slicing") assert.Equal(slice14to4[3], []byte{13, 14}, "Bad Slicing") fithteen := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} slice15to4 := sliceTo(4, fithteen) assert.Equal(4, len(slice15to4), "Bad Slicing") assert.Equal(slice15to4[0], []byte{1, 2, 3, 4}, "Bad Slicing") assert.Equal(slice15to4[1], []byte{5, 6, 7, 8}, "Bad Slicing") assert.Equal(slice15to4[2], []byte{9, 10, 11, 12}, "Bad Slicing") assert.Equal(slice15to4[3], []byte{13, 14, 15}, "Bad Slicing") sixteen := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} slice16to4 := sliceTo(4, sixteen) assert.Equal(4, len(slice16to4), "Bad Slicing") assert.Equal(slice16to4[0], []byte{1, 2, 3, 4}, "Bad Slicing") assert.Equal(slice16to4[1], []byte{5, 6, 7, 8}, "Bad Slicing") assert.Equal(slice16to4[2], []byte{9, 10, 11, 12}, "Bad Slicing") assert.Equal(slice16to4[3], []byte{13, 14, 15, 16}, "Bad Slicing") seventeen := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17} slice17to4 := sliceTo(4, seventeen) assert.Equal(5, len(slice17to4), "Bad Slicing") assert.Equal(slice17to4[0], []byte{1, 2, 3, 4}, "Bad Slicing") assert.Equal(slice17to4[1], []byte{5, 6, 7, 8}, "Bad Slicing") assert.Equal(slice17to4[2], []byte{9, 10, 11, 12}, "Bad Slicing") assert.Equal(slice17to4[3], []byte{13, 14, 15, 16}, "Bad Slicing") assert.Equal(slice17to4[4], []byte{17}, "Bad Slicing") } func getRTPHeader(mark bool) []byte { rtpMarkerBitMask := byte(0x80) rtpHeader := []byte{0x80, 0x62, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x70, 0x1c, 0x77, 0x48} if mark { rtpHeader[1] |= rtpMarkerBitMask } return rtpHeader } func getRTPHeaderOffSet(offset uint16, mark bool) []byte { rtpHeader := getRTPHeader(mark) seqNum := binary.BigEndian.Uint16(rtpHeader[2:4]) binary.BigEndian.PutUint16(rtpHeader[2:4], seqNum+offset) return rtpHeader } func getH264StapADelimiterAndSpsAndPps() []byte { return []byte{ 0x18, // stap-a 0x00, 0x02, 0x09, 0xF0, // delimiter 0x00, 0x19, 0x67, 0x4D, 0x40, 0x2A, 0xEC, 0xA0, 0x3C, 0x01, 0x13, 0xF2, 0xC2, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0xF1, 0x1E, 0x30, 0x63, 0x2C, // sps 0x00, 0x04, 0x68, 0xEF, 0xBC, 0x80, // pps } } func getH264DelimiterNal() []byte { return []byte{0x09, 0xF0} } func getH264SpsNal() []byte { return []byte{ 0x67, 0x4D, 0x40, 0x2A, 0xEC, 0xA0, 0x3C, 0x01, 0x13, 0xF2, 0xC2, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0xF1, 0x1E, 0x30, 0x63, 0x2C, } } func getH264PpsNal() []byte { return []byte{0x68, 0xEF, 0xBC, 0x80} } func getRtpsFuaNonIdr() [][]byte { // nolint: maintidx return [][]byte{ { // nolint: dupl 0x80, 0x60, 0x75, 0x25, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x81, 0x9a, 0xf8, 0x6e, 0xb9, 0x35, 0xb1, 0xbe, 0x6d, 0x9f, 0xe4, 0xf5, 0x3f, 0x26, 0xc7, 0x97, 0xbb, 0x06, 0x82, 0x65, 0x17, 0x78, 0x43, 0x2f, 0xe2, 0xb2, 0x5f, 0x5e, 0xbc, 0x31, 0x13, 0x58, 0x67, 0x05, 0x11, 0x39, 0x7c, 0xb0, 0x9f, 0xe0, 0xbf, 0xe5, 0xf2, 0xc9, 0x59, 0x72, 0x78, 0x99, 0x70, 0xc7, 0x84, 0x60, 0x9b, 0x06, 0x10, 0x86, 0x49, 0x70, 0xcd, 0x7a, 0xc9, 0x06, 0xb5, 0x97, 0x2d, 0x70, 0x45, 0xd1, 0xb0, 0x71, 0x5f, 0x7d, 0x05, 0xf1, 0x3d, 0x3a, 0x73, 0x06, 0xbe, 0x2f, 0xa1, 0x7b, 0xe5, 0x84, 0xfb, 0xbb, 0xe9, 0x79, 0xa9, 0x65, 0x65, 0x73, 0x50, 0x2f, 0xe2, 0x3b, 0x5c, 0x7c, 0xb8, 0x7f, 0x15, 0xcc, 0x73, 0xd8, 0x13, 0xe1, 0xae, 0x52, 0x55, 0xdb, 0xff, 0x85, 0xbc, 0xe4, 0x68, 0x7a, 0xa7, 0xfd, 0xff, 0x27, 0x97, 0xcb, 0x36, 0x48, 0xac, 0x9e, 0x59, 0x65, 0xc6, 0xe5, 0x9a, 0x8b, 0x50, 0x30, 0x62, 0xf2, 0xcb, 0x90, 0x55, 0xbf, 0xd7, 0x90, 0x2d, 0xeb, 0x28, 0x46, 0x80, 0xb5, 0xdf, 0xeb, 0x18, 0x26, 0x84, 0xbe, 0xc2, 0xf1, 0xa2, 0x7a, 0x17, 0x3a, 0x5f, 0xba, 0x01, 0xa3, 0x62, 0x21, 0x07, 0xc7, 0xe8, 0xf6, 0x7f, 0xa0, 0x1e, 0xca, 0x0b, 0x32, 0x23, 0x7d, 0x7f, 0x28, 0x6a, 0x0f, 0x96, 0x50, 0xd6, 0x81, 0x7c, 0x4c, 0xa1, 0x8f, 0x3a, 0x8f, 0xb8, 0x8d, 0x02, 0xd8, 0x36, 0x02, 0xf5, 0x7a, 0x5b, 0xe1, 0x1e, 0xc5, 0xd8, 0x1f, 0x65, 0xe3, 0x6f, 0x2f, 0xfc, 0x90, 0x43, 0x87, 0x63, 0xb2, 0x46, 0xe1, 0xd1, 0x1b, 0x16, 0xff, 0x70, 0xf7, 0x45, 0x23, 0xf6, 0x79, 0x49, 0x30, 0x12, 0xca, 0x6e, 0x97, 0xea, 0x18, 0xe6, 0x04, 0x09, 0x4c, 0x7a, 0x6b, 0x41, 0x7f, 0xe1, 0xbd, 0xd9, 0xd7, 0xb0, 0xfd, 0xf0, 0x8f, 0x1a, 0x09, 0xfe, 0x55, 0xe9, 0xe7, 0xe3, 0x34, 0xba, 0x5d, 0xec, 0x6c, 0x7f, 0xcd, 0xb0, 0x1c, 0xa2, 0x9e, 0x12, 0xf2, 0x0f, 0x20, 0xff, 0xc6, 0xd8, 0x36, 0x5c, 0x74, 0x30, 0xff, 0x21, 0x46, 0x02, 0x6b, 0x46, 0xac, 0x1f, 0xff, 0x0e, 0xec, 0xeb, 0x8c, 0x06, 0x9f, 0x40, 0xeb, 0xdf, 0xfc, 0xbd, 0x8b, 0xe1, 0x0e, 0x87, 0xde, 0x60, 0xbc, 0xa9, 0xfc, 0xd4, 0xf9, 0xb2, 0xf9, 0x61, 0x9b, 0xc3, 0xd7, 0x21, 0x75, 0x93, 0x0c, 0x85, 0x3d, 0x88, 0xff, 0xfa, 0x03, 0xaf, 0xdf, 0x0e, 0xec, 0x5e, 0xec, 0x3d, 0x04, 0x95, 0x5e, 0xff, 0x5c, 0x15, 0xd8, 0x13, 0x2b, 0xb1, 0x58, 0x0c, 0x70, 0x70, 0x25, 0xbc, 0xbc, 0x82, 0x79, 0x82, 0xfd, 0x8b, 0xe2, 0xbc, 0xc1, 0x02, 0xfd, 0x3f, 0x1b, 0xbd, 0xae, 0x07, 0x6c, 0x2c, 0xf0, 0xec, 0x0f, 0x23, 0x07, 0x48, 0xef, 0xd3, 0xbf, 0xbb, 0x7f, 0xdc, 0x4e, 0x30, 0xbf, 0xdf, 0xca, 0x2b, 0x1c, 0x13, 0xef, 0x9c, 0x10, 0xf9, 0xe3, 0xd5, 0x22, 0x2c, 0x8f, 0x8d, 0xf7, 0xa6, 0x83, 0xff, 0x47, 0x7c, 0xc1, 0x95, 0x6a, 0xcf, 0xfb, 0xfc, 0x13, 0x59, 0x7e, 0xae, 0x7c, 0x29, 0xd0, 0xd0, 0x41, 0xf9, 0x56, 0x71, 0x7b, 0xe1, 0x33, 0x06, 0x1c, 0x6d, 0xf1, 0xbb, 0x02, 0xb5, 0x47, 0x38, 0xfe, 0xd5, 0x7c, 0xa2, 0x11, 0xe1, 0xd2, 0xff, 0x87, 0x7a, 0x06, 0xc9, 0x87, 0x6b, 0x74, 0x0c, 0x03, 0xaa, 0xfa, 0x30, 0xef, 0xff, 0x0b, 0xf2, 0xa4, 0x21, 0x27, 0xd0, 0x24, 0x3d, 0x6c, 0x2f, 0xfc, 0x4f, 0x4e, 0x19, 0x45, 0xd0, 0xdd, 0x5b, 0xca, 0xbd, 0xe4, 0x9b, 0x24, 0x4e, 0x1c, 0x88, 0xcb, 0x37, 0x37, 0xb1, 0x3a, 0xc9, 0x8d, 0x86, 0x38, 0x4d, 0x72, 0x86, 0xfa, 0x0b, 0x94, 0x2d, 0xa1, 0x58, 0xa5, 0xb0, 0x99, 0x80, 0xcd, 0xbc, 0x89, 0x72, 0xa1, 0xff, 0x19, 0xe5, 0x19, 0xe6, 0x5f, 0xd9, 0x74, 0x1e, 0x54, 0x03, 0xa1, 0x23, 0xc4, 0xc6, 0xc5, 0x36, 0xd1, 0x1a, 0x16, 0x7e, 0x1f, 0x90, 0x3f, 0x62, 0x9d, 0x69, 0x87, 0xd0, 0x2d, 0x82, 0x69, 0x74, 0xff, 0xe1, 0xfe, 0x81, 0x48, 0x7d, 0xff, 0x40, 0xd0, 0x09, 0x6d, 0xff, 0xe6, 0xe1, 0xd9, 0xea, 0x7c, 0xfc, 0x4f, 0x62, 0xb1, 0x53, 0xc5, 0xf1, 0xfc, 0x6c, 0xa1, 0x8d, 0x01, 0x6c, 0x6d, 0x61, 0xe8, 0x24, 0x1e, 0x28, 0xe1, 0x6a, 0x91, 0x7f, 0xf5, 0xaf, 0xfe, 0x08, 0x3a, 0x05, 0xf6, 0x5c, 0x85, 0x15, 0x03, 0xff, 0xe2, 0xef, 0x80, 0x67, 0x22, 0x8f, 0xdf, 0x1b, 0xc0, 0x2d, 0x81, 0xd8, 0xcc, 0x8b, 0xba, 0x01, 0xfa, 0xdf, 0xff, 0x15, 0x21, 0xaf, 0xd8, 0x14, 0xd9, 0x66, 0xea, 0xf0, 0x9e, 0x48, 0xcc, 0xb3, 0x65, 0x93, 0x24, 0xb8, 0x42, 0xef, 0x59, 0x6a, 0x23, 0x7c, 0x33, 0xb5, 0xad, 0x3f, 0xcb, 0x1f, 0xa9, 0xe4, 0xc8, 0x31, 0xc9, 0xc9, 0xa0, 0x7a, 0xc9, 0x59, 0x62, 0x32, 0xde, 0x5a, 0xc9, 0x11, 0x92, 0x23, 0xae, 0x51, 0x3d, 0x37, 0x9e, 0xb1, 0x59, 0x6a, 0xbe, 0x42, 0xfa, 0xf2, 0xaf, 0x44, 0x65, 0xac, 0x91, 0xb9, 0x6b, 0x0c, 0xcb, 0xc9, 0x5e, 0xef, 0xe4, 0x92, 0xf1, 0x55, 0x89, 0xc4, 0xc5, 0x50, 0x57, 0xbf, 0x94, 0xcd, 0x2f, 0xc9, 0x79, 0x27, 0xc4, 0x79, 0x7c, 0x91, 0x35, 0xfa, 0xd7, 0x92, 0xb9, 0xb6, 0x05, 0xab, 0xc4, 0xe1, 0x5f, 0x2c, 0x25, 0x92, 0x6c, 0x95, 0x82, 0x92, 0x6b, 0xf2, 0x78, 0x3d, 0xae, 0xca, 0x8f, 0x93, 0x9b, 0xb7, 0xf2, 0xfa, 0x6a, 0xff, 0x5e, 0xa1, 0x3f, 0x82, 0x5f, 0x27, 0x87, 0x0d, 0x7e, 0x4c, 0xab, 0x2b, 0xc1, 0x49, 0xbd, 0xf8, 0x20, 0x7e, 0xfc, 0xa6, 0xf7, 0xc2, 0x37, 0xa1, 0xeb, 0x92, 0xff, 0xd5, 0xf8, 0xbc, 0xfd, 0x62, 0xfa, 0xb5, 0x72, 0x5f, 0xf0, 0x63, 0x7d, 0x5e, 0x4c, 0x10, 0x57, 0x04, 0x9d, 0xbf, 0x7c, 0xde, 0xc6, 0x6c, 0x10, 0x97, 0xb2, 0xf9, 0xb4, 0xbe, 0x25, 0xf5, 0xe0, 0x60, 0xfc, 0x95, 0xfc, 0xbf, 0x3d, 0x5b, 0xff, 0xe5, 0xf7, 0x7c, 0xda, 0x3d, 0xf2, 0x77, 0xbe, 0x08, 0xbd, 0x5f, 0xe4, 0xdb, 0xf6, 0x4f, 0x2b, 0xaf, 0xf0, 0x8d, 0x3f, 0xdf, 0x40, 0x3b, 0xc1, 0x4f, 0x82, 0x8a, 0x84, 0xb5, 0x81, 0x25, 0x59, 0xf5, 0x83, 0xe6, 0xb7, 0xfd, 0x5f, 0xef, 0xa7, 0xe2, 0x25, 0xc2, 0x1e, 0x26, 0xb2, 0x54, 0x69, 0x2b, 0x07, 0x3e, 0x0a, 0x3e, 0xfb, 0xf9, 0x03, 0x9e, 0xee, 0xd3, 0xff, 0x93, 0x97, 0x5c, 0xbd, 0x03, 0xae, 0xad, 0xe2, 0x3c, 0x22, 0x5e, 0x87, 0xe2, 0x39, 0x03, 0x37, 0xc9, 0xd7, 0x85, 0x7d, 0x7c, 0x29, 0xf2, 0x6f, 0xef, 0xf5, 0x95, 0x48, 0x24, 0xdd, 0x7c, 0x23, 0x5c, 0xf5, 0xf4, 0xf9, 0x25, 0xc9, 0xd6, 0xb9, 0x26, 0xf6, 0x3c, 0xba, 0xc1, 0xcf, 0x93, 0xc8, 0x23, 0xd0, 0x5a, 0x5e, 0x11, 0xf1, 0xa6, 0xeb, 0xf3, 0x75, 0xac, 0xa6, 0xb1, 0xf2, 0x64, 0xae, 0x6d, 0x17, 0xc3, 0x32, 0x72, 0xef, 0xac, 0x21, 0xe1, 0x1c, 0x2b, 0x8e, 0x1b, 0x3f, 0xff, 0x63, 0xac, 0x85, 0xf4, 0xb5, 0xfb, 0xd7, 0x50, 0x45, 0xaa, 0xbe, 0xf7, 0xd5, 0xe7, 0x97, 0xf2, 0xd2, 0xfc, 0x10, 0x1a, 0xff, 0x81, 0x17, 0xc4, 0xd5, 0x13, 0xc1, 0xe5, 0x5f, 0xc1, 0x29, 0xbc, 0x3c, 0x83, 0x61, 0xca, 0x10, 0xbe, 0x6d, 0x77, 0x96, 0x5c, 0x9e, 0x11, 0xac, 0x41, 0x7d, 0x0d, 0x75, 0xca, 0xb0, 0x81, 0x7a, 0xd6, 0x15, 0x93, 0x1a, 0xfd, 0x1f, 0x21, 0x7d, 0xd4, 0x81, 0x5e, 0x10, 0x0a, 0x02, 0x4e, 0xdf, 0x29, 0x32, 0x76, 0x21, 0x75, 0x5c, 0xda, 0xe6, 0xa9, 0x3a, 0x19, 0xe1, 0xef, 0xd3, 0xf7, 0xa2, 0xf8, 0x66, 0x4c, 0x6f, 0x8c, 0xf0, 0xe7, 0x92, 0xb8, 0x23, 0xd7, 0x8b, 0xe0, 0x8b, 0x40, 0xfe, 0xf2, 0x4b, 0x75, 0x72, 0xaf, 0xc0, 0xc2, 0x09, 0x3c, 0x47, 0x97, 0xc6, 0xf9, 0x01, 0x1e, 0xbc, 0x52, 0xe4, 0x57, 0xf2, 0x17, 0xd2, 0xff, 0x2f, 0xcf, 0x5e, 0x9f, 0xbc, 0x9e, 0x5f, 0x0c, 0xd7, 0x75, 0xeb, 0x27, 0xc1, 0x76, 0xba, 0x2f, 0x84, 0x98, 0xd0, 0x4f, 0xae, 0x87, 0xe0, 0xf1, 0x85, 0xd7, 0x58, 0x9f, 0x04, 0x9f, 0x57, 0x7d, 0xf3, 0x81, 0xfb, 0xd6, 0x49, 0x72, 0x9b, 0xd5, 0xcb, 0x37, 0x6a, 0x5c, 0x9e, 0x27, 0xc9, 0xe2, 0x01, 0x27, 0x4b, 0xdf, 0x36, 0x87, 0xe8, 0xb2, 0x65, 0xf2, 0x3d, 0xbd, 0x58, 0x63, 0x8e, 0xd7, 0x2d, 0x7a, 0xc6, 0xf8, 0xdf, 0x27, 0x90, 0xaf, 0xa0, 0x6a, 0x4f, 0x72, 0x7a, 0x5e, 0x08, 0x7d, 0xea, 0xf0, 0x89, 0xb4, 0x7e, 0x49, 0xea, 0xdf, 0xfe, 0x34, 0xdd, 0x3e, 0xab, 0x2f, 0x1a, 0x5f, 0x7e, 0x43, 0xd7, 0xa2, 0xf7, 0xc1, 0x0e, 0xfc, 0x1e, 0x50, 0x9f, 0x67, 0xaf, 0xe4, 0xf3, 0x2f, 0xeb, 0x2e, 0xaa, 0xf5, 0x94, 0x5f, 0xb6, 0xe3, 0xa0, 0x00, 0x40, 0x31, 0xec, 0x3d, 0x88, 0x9e, 0x8b, 0x1b, 0x8d, 0xec, 0xc6, 0xdc, 0xb3, 0x41, 0xe5, 0x05, 0x5b, 0xa6, 0x42, 0x9f, 0xe2, 0x2f, 0xe9, 0xd5, 0xbf, 0xe8, 0xfd, 0x4d, 0x24, 0xda, 0x14, 0x4c, 0xa9, 0x98, 0x6a, 0xaf, 0xd4, 0x6f, 0x56, 0x10, 0x22, 0x92, 0x0d, 0x12, 0xe7, 0x35, 0x6b, 0xc8, 0x86, 0x92, 0xb2, 0xc8, 0xad, 0xc5, 0x3c, 0x4b, 0x4a, 0x3a, 0xa0, 0xff, 0xc3, 0x15, 0x20, 0x77, 0xa4, 0x5d, 0xa5, 0x36, 0xf7, 0x5d, 0x2f, 0xf0, 0x42, 0xaf, 0xf5, 0xd7, 0xc6, 0xfa, 0x54, 0x66, 0x10, 0x34, 0x14, 0x3c, 0x87, 0x2e, 0xac, 0x48, 0xb8, 0x8c, 0x36, 0xb1, 0xb0, 0xef, 0xfb, 0x20, 0xde, 0x5c, 0x29, 0x07, 0x28, 0xc3, 0x23, 0x68, 0xdd, 0xef, 0x01, 0x54, 0xbe, 0xe5, 0x33, 0x4f, 0xff, 0x00, 0x75, 0xdf, 0x50, 0x0f, 0xac, 0x7e, 0x70, 0xcd, 0x79, 0xbe, 0xaf, 0xa2, 0x02, 0x0f, 0x7e, 0x28, 0xea, 0x24, 0x5b, 0x3b, 0xed, 0xff, 0xca, 0x5f, 0x6f, 0x51, 0xbe, 0x3c, 0xcb, 0x48, 0xcc, 0x84, 0xc8, 0x60, 0x50, 0x69, 0x60, 0x20, 0x04, 0x2a, 0xfe, 0x66, 0x0e, 0x01, 0xd8, 0xc9, 0x98, 0xef, 0x5b, 0x8a, 0x2c, 0xa6, 0xbf, 0xf1, 0xbd, 0x49, 0x04, 0x51, 0xa6, 0xba, 0xb2, 0x5b, 0xd4, 0x75, 0x06, 0xbf, 0x41, 0xd5, 0xf9, 0x93, 0x33, 0x36, 0x05, 0xbe, 0xd4, 0xa5, 0x13, 0xba, 0x32, 0x90, 0x7f, 0x94, 0x23, 0xbc, 0x80, 0x8c, 0x01, 0xe0, 0x9b, 0x8d, 0x88, 0x9a, 0x54, 0x59, 0x0f, 0x1b, 0x40, 0x22, 0x12, 0x7a, 0x2c, 0x35, 0x0c, 0x1a, 0x99, 0xc6, 0x9f, 0x08, 0x97, 0x4c, 0x0a, 0xbb, 0x5c, 0x0a, 0x1e, 0x13, 0x6c, 0x45, 0x63, 0xa0, 0x51, 0x07, 0x51, 0x1c, 0x35, 0xa3, 0x7a, 0xee, 0xbe, 0x27, 0x1f, }, { // nolint: dupl 0x80, 0x60, 0x75, 0x26, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x01, 0xa2, 0xbe, 0xdf, 0x11, 0xac, 0x22, 0xb1, 0x73, 0x01, 0x03, 0xc0, 0x82, 0xfb, 0x5e, 0x22, 0xbb, 0xf5, 0x58, 0xd2, 0xe1, 0x13, 0x03, 0xfd, 0xbf, 0x08, 0x9b, 0xa2, 0xec, 0xa0, 0x9f, 0xb4, 0x60, 0x4d, 0x2a, 0x98, 0xbe, 0x6b, 0xf5, 0xdd, 0xfe, 0xef, 0xaf, 0x61, 0xda, 0xc3, 0xa6, 0xf5, 0xc9, 0x0c, 0xed, 0xeb, 0xfa, 0xf0, 0x95, 0x4b, 0x77, 0xf1, 0x8b, 0xdd, 0x82, 0x0f, 0x2d, 0x61, 0x93, 0x6b, 0xf0, 0xa9, 0x7a, 0x5e, 0x45, 0x83, 0xe1, 0x2a, 0x01, 0x76, 0x2f, 0xe6, 0xbf, 0x58, 0xd7, 0xed, 0x72, 0x5f, 0x08, 0xfa, 0xec, 0x7e, 0xd2, 0xe5, 0xf5, 0x5c, 0xd7, 0xeb, 0x08, 0x17, 0xca, 0x4b, 0x89, 0x32, 0xe6, 0x1f, 0xc0, 0xf1, 0x9a, 0x07, 0x1d, 0xf3, 0x7a, 0x6b, 0x1b, 0xe0, 0x51, 0x3d, 0x68, 0xbf, 0xe2, 0x2f, 0x93, 0xd0, 0xdf, 0x57, 0xf9, 0xb7, 0xfc, 0x2f, 0x1f, 0x06, 0x8f, 0x99, 0x97, 0x94, 0x91, 0x75, 0x9e, 0x7d, 0xfd, 0x43, 0xb0, 0xde, 0x3b, 0xa4, 0x9e, 0x7e, 0x07, 0xfb, 0xed, 0x41, 0xa3, 0x83, 0xbe, 0xb4, 0xd0, 0x5f, 0xee, 0x37, 0x8a, 0xa2, 0x1c, 0x84, 0xec, 0x67, 0xf9, 0xc3, 0x4b, 0x45, 0x42, 0xda, 0x3a, 0x65, 0x72, 0xd6, 0x4e, 0xb3, 0x74, 0xd4, 0x2f, 0xf2, 0x0d, 0xe0, 0x1e, 0xbb, 0xbf, 0x8c, 0xf8, 0xd6, 0x3b, 0xfb, 0x25, 0xd4, 0xb4, 0x09, 0x40, 0xd2, 0x4a, 0x89, 0xab, 0x03, 0x1d, 0xe1, 0x00, 0x70, 0xc3, 0x2b, 0xab, 0xeb, 0xbf, 0x5e, 0x40, 0x53, 0x57, 0x06, 0xff, 0x15, 0x98, 0x4d, 0x08, 0x7c, 0x44, 0x2c, 0x1c, 0x2e, 0x39, 0x43, 0xba, 0x8f, 0xa1, 0xb9, 0x3c, 0xa3, 0x49, 0x46, 0x7a, 0xa1, 0x72, 0xa9, 0xae, 0x3d, 0x32, 0xe3, 0x60, 0x3b, 0x42, 0x11, 0x38, 0x88, 0x72, 0xe2, 0x53, 0x32, 0xb3, 0xfe, 0x16, 0xac, 0x3d, 0xdc, 0x26, 0x10, 0x66, 0xcb, 0x68, 0x6a, 0xb7, 0x86, 0x9d, 0xd9, 0x7f, 0xc2, 0xfe, 0x75, 0xa0, 0xec, 0x9a, 0x1b, 0x82, 0xd1, 0x6b, 0x47, 0xfe, 0x37, 0x43, 0x88, 0xd1, 0xc3, 0xd4, 0xfc, 0x24, 0xe3, 0x36, 0x7e, 0xdc, 0x7c, 0x43, 0xec, 0x65, 0x59, 0xa4, 0xc1, 0xca, 0x28, 0xdd, 0xff, 0xc3, 0x03, 0x6f, 0x59, 0x2b, 0x47, 0xc1, 0x35, 0x80, 0x9d, 0x91, 0xa3, 0x74, 0x21, 0x66, 0x4d, 0xf0, 0xec, 0x93, 0x36, 0x54, 0xad, 0xed, 0xc9, 0xff, 0x12, 0x3f, 0x47, 0xf0, 0x26, 0xc3, 0xc8, 0x37, 0x52, 0x0f, 0xd1, 0xfc, 0x1f, 0x8d, 0xbf, 0x2f, 0xaa, 0x7b, 0xaa, 0xc8, 0xf9, 0xed, 0xa4, 0x34, 0x45, 0x42, 0xf1, 0x03, 0x46, 0x89, 0x11, 0x27, 0x79, 0x63, 0xfa, 0x86, 0x25, 0xc6, 0x0d, 0xc4, 0x16, 0x1a, 0x2d, 0x06, 0x59, 0x25, 0x5d, 0x67, 0x49, 0x7b, 0x3f, 0xdc, 0x31, 0x48, 0xb5, 0x0a, 0x5f, 0x18, 0x36, 0x83, 0x38, 0x6a, 0x1c, 0xdc, 0x7d, 0xd3, 0xf6, 0x7e, 0x09, 0x3d, 0x09, 0xdf, 0x17, 0x28, 0xa1, 0xd2, 0x42, 0x60, 0x34, 0xb9, 0x79, 0x61, 0xa1, 0x1f, 0xc1, 0x46, 0x72, 0x74, 0x6d, 0x48, 0x7a, 0x06, 0xd0, 0xdc, 0x16, 0x63, 0x10, 0x31, 0xf0, 0x87, 0x63, 0xb2, 0xec, 0x6a, 0x19, 0x87, 0xad, 0x48, 0x30, 0x3e, 0xb8, 0x31, 0x04, 0x7d, 0xa1, 0x80, 0xe2, 0xf8, 0xbf, 0x0a, 0x25, 0xd4, 0x64, 0x8f, 0x40, 0x86, 0x65, 0xe1, 0x8b, 0xc1, 0x01, 0x12, 0x96, 0xab, 0x0c, 0x2f, 0xf0, 0xb9, 0xf8, 0xd8, 0xed, 0xcb, 0x4d, 0xaf, 0xf8, 0x4b, 0xdd, 0x1d, 0x79, 0x39, 0x58, 0x31, 0xe1, 0x11, 0x9d, 0x11, 0xf6, 0x7f, 0x36, 0x68, 0xeb, 0x0c, 0xf7, 0x7d, 0x2e, 0xb5, 0xcb, 0xde, 0xa5, 0x9a, 0xff, 0x83, 0x0f, 0x1b, 0xe1, 0x1f, 0x04, 0x1e, 0x4f, 0xab, 0xc9, 0xcd, 0xdb, 0xbb, 0x0a, 0x79, 0x7c, 0x23, 0x53, 0xeb, 0x2a, 0xf7, 0xd6, 0xbe, 0xfb, 0x5e, 0x0a, 0x7c, 0x14, 0xf9, 0x3c, 0x9c, 0x9e, 0x4c, 0xd9, 0x24, 0xe8, 0x8d, 0x79, 0x6f, 0x24, 0x9c, 0xde, 0x53, 0x1f, 0x1b, 0xd0, 0x27, 0x63, 0x65, 0xa2, 0x98, 0x33, 0x6e, 0x67, 0xb7, 0x0e, 0x24, 0x96, 0xba, 0xff, 0xf0, 0xb7, 0x1a, 0x21, 0xb1, 0xac, 0xa8, 0xb8, 0x04, 0xc6, 0xad, 0xff, 0x79, 0x41, 0x4f, 0x4a, 0x88, 0xcd, 0xeb, 0x6c, 0x7a, 0x15, 0x01, 0xcc, 0x12, 0xef, 0x8c, 0xfb, 0x01, 0xf0, 0xfe, 0xf9, 0xcc, 0x12, 0xa7, 0xcc, 0x82, 0x83, 0xf0, 0xa4, 0xbd, 0xe9, 0xc7, 0x04, 0x0f, 0x74, 0x37, 0xb1, 0xb0, 0x52, 0x50, 0xd7, 0x2f, 0x8c, 0xd8, 0x07, 0x42, 0x50, 0x7e, 0x80, 0xde, 0xab, 0x99, 0x94, 0x05, 0xc2, 0xf8, 0xcf, 0x41, 0x99, 0xf4, 0x0b, 0xa1, 0xa7, 0xa4, 0x70, 0xc1, 0xe8, 0x0b, 0x8c, 0x19, 0xe9, 0xec, 0x56, 0x03, 0x44, 0xe8, 0xb4, 0x3b, 0x0b, 0x90, 0x7f, 0x62, 0xd0, 0x0f, 0xb7, 0xf8, 0xda, 0x1f, 0xd0, 0x7e, 0xdf, 0x87, 0x65, 0xdc, 0xad, 0xc6, 0x8b, 0xfa, 0x8e, 0xd7, 0x53, 0x61, 0xf4, 0xfb, 0xf9, 0xa3, 0x3e, 0x4a, 0x89, 0x83, 0x82, 0x00, 0xff, 0xc1, 0x7e, 0x18, 0x24, 0xf8, 0x0c, 0x33, 0x86, 0x57, 0xf7, 0xb6, 0x09, 0xbd, 0x05, 0x6b, 0x31, 0xbf, 0xf1, 0xbe, 0x3e, 0x79, 0xe8, 0x0b, 0x2b, 0x84, 0x37, 0xfb, 0xae, 0xcf, 0xfe, 0x36, 0x85, 0xd1, 0x51, 0xa1, 0xc1, 0x3b, 0x15, 0x87, 0xea, 0xb4, 0xe9, 0xff, 0x8d, 0xbc, 0xde, 0x8d, 0x0b, 0x12, 0x8f, 0x98, 0x82, 0xc0, 0xc8, 0x3a, 0x40, 0x80, 0x7d, 0x3d, 0x6b, 0xff, 0x8d, 0xe3, 0xc1, 0xf3, 0xc3, 0xda, 0x18, 0x0b, 0x45, 0x4e, 0x0b, 0x4e, 0xb4, 0xd3, 0xbd, 0x9f, 0xc2, 0x14, 0x88, 0xb9, 0x3a, 0x18, 0x6b, 0x3d, 0x02, 0x61, 0x22, 0x42, 0xd8, 0xfe, 0x14, 0xd3, 0x5d, 0x49, 0x69, 0xfc, 0xca, 0x50, 0x73, 0xb3, 0xa0, 0xec, 0x09, 0x8f, 0x85, 0x39, 0xc9, 0xf8, 0xc6, 0xfe, 0x7c, 0x28, 0xf6, 0x96, 0xff, 0x13, 0xe1, 0x37, 0x28, 0xd7, 0xe0, 0x87, 0xc6, 0xc9, 0x2f, 0xb0, 0xef, 0x7b, 0x93, 0xc9, 0xf1, 0x95, 0x97, 0xcb, 0x1d, 0xd6, 0xab, 0x11, 0xe0, 0xa2, 0xbf, 0x96, 0x2e, 0x20, 0xbe, 0x36, 0xf9, 0x35, 0xd6, 0x4f, 0x2d, 0xd4, 0xde, 0x34, 0x98, 0xf1, 0xbb, 0x1c, 0xc0, 0xc4, 0x02, 0xad, 0x81, 0x86, 0xa2, 0x04, 0x02, 0x9a, 0x2e, 0x49, 0xae, 0x0f, 0x80, 0xff, 0xb0, 0xd6, 0x39, 0xd7, 0xd3, 0xfc, 0x77, 0xd0, 0xf0, 0x09, 0x86, 0xc5, 0x1a, 0x53, 0x86, 0xa0, 0xfc, 0xbe, 0xc9, 0xe0, 0x90, 0x3f, 0xec, 0x3a, 0x0e, 0xc6, 0xd7, 0x54, 0x7f, 0xf9, 0x42, 0x38, 0x21, 0x68, 0x79, 0x56, 0x04, 0x05, 0x8a, 0x3c, 0x0f, 0x7d, 0x8b, 0xf1, 0xbf, 0x4d, 0x81, 0x30, 0xf6, 0x1c, 0xdf, 0x6f, 0x8a, 0xda, 0xff, 0xa8, 0xdc, 0x72, 0x71, 0x67, 0xb2, 0xd0, 0xee, 0x95, 0xc6, 0x55, 0xce, 0xff, 0xf1, 0x92, 0x06, 0x06, 0x00, 0xc9, 0x2e, 0x43, 0x51, 0xe1, 0x2d, 0x81, 0x7f, 0xb0, 0x3e, 0x19, 0x0b, 0xe1, 0xb7, 0x79, 0xf4, 0x38, 0x40, 0xe1, 0xee, 0x2b, 0xfa, 0xf9, 0x05, 0x6c, 0x56, 0xb9, 0x03, 0x5c, 0x60, 0x2c, 0xdc, 0x1c, 0xd8, 0xff, 0x2a, 0xe4, 0x51, 0x3a, 0xb8, 0x2a, 0xd0, 0x98, 0xef, 0xce, 0x93, 0xb9, 0xe4, 0x1d, 0x65, 0x45, 0xb1, 0xca, 0x3c, 0x26, 0x06, 0x4c, 0x81, 0x1f, 0x8c, 0x0e, 0xd9, 0xe0, 0xf3, 0x06, 0x0d, 0x08, 0xa6, 0x65, 0x57, 0xa6, 0x55, 0xff, 0xde, 0x8b, 0xf2, 0x87, 0xb4, 0x3b, 0x0b, 0x45, 0x42, 0xfb, 0xbf, 0xaf, 0x94, 0x4e, 0xc2, 0xb1, 0xc6, 0xe6, 0x34, 0x5e, 0x0a, 0xec, 0x07, 0x60, 0x43, 0x41, 0x7a, 0xf2, 0x1d, 0x42, 0xd0, 0xcf, 0xa0, 0xab, 0xb8, 0xd9, 0xe7, 0xec, 0x7a, 0x03, 0xbc, 0xc0, 0xba, 0x7a, 0xa0, 0xe4, 0xdf, 0xec, 0xa2, 0x78, 0x51, 0x21, 0x6e, 0x42, 0xc5, 0x32, 0x8e, 0x10, 0x25, 0x2e, 0x5a, 0x0e, 0x75, 0x56, 0xac, 0x97, 0x8d, 0x04, 0xde, 0x36, 0x25, 0x68, 0x0f, 0x7f, 0x1b, 0x58, 0x24, 0xf0, 0xcd, 0x70, 0x45, 0xec, 0x6e, 0xac, 0xb5, 0x92, 0xb0, 0x71, 0xe5, 0xf2, 0xd5, 0x06, 0x6a, 0xb7, 0x82, 0x9f, 0x02, 0x2f, 0x93, 0xc0, 0x81, 0xc9, 0xe2, 0xb5, 0xcd, 0x30, 0x2f, 0xaa, 0xc9, 0x93, 0xc9, 0x11, 0xc9, 0xe8, 0x1a, 0xe1, 0x1e, 0x00, 0x98, 0x0f, 0x21, 0xe9, 0x43, 0x55, 0xa7, 0xfc, 0x3d, 0xda, 0xe7, 0x89, 0x82, 0x70, 0x1b, 0x1f, 0x56, 0x7f, 0xfb, 0x8d, 0xcd, 0xce, 0x08, 0x1e, 0xcc, 0xd3, 0x8a, 0xfb, 0xd7, 0x49, 0x7f, 0xc2, 0x9d, 0x84, 0x96, 0x49, 0x48, 0x41, 0x78, 0x8a, 0x54, 0xa1, 0xd2, 0xf5, 0xe4, 0x13, 0x87, 0xd2, 0x70, 0xb4, 0x2f, 0xd8, 0x07, 0x8d, 0x13, 0xa0, 0x52, 0x3e, 0x3e, 0x26, 0x18, 0xca, 0xa0, 0x78, 0x0f, 0xc8, 0x1f, 0xa0, 0xdb, 0x8e, 0xa1, 0x5a, 0x18, 0xc7, 0x44, 0xe6, 0x45, 0x7c, 0x0d, 0xc9, 0x9c, 0x1a, 0x7f, 0xf8, 0x20, 0xd4, 0x4f, 0x5e, 0xb6, 0xb3, 0x86, 0x6b, 0xd1, 0x7f, 0x20, 0xad, 0xe5, 0x0e, 0x58, 0xe8, 0x07, 0xc4, 0x8c, 0xb1, 0xda, 0x25, 0xd1, 0x58, 0x12, 0xad, 0x3e, 0xd3, 0x0d, 0x18, 0xc2, 0x63, 0x89, 0x15, 0xe4, 0x5e, 0x80, 0xbf, 0x0b, 0xec, 0x0f, 0xee, 0x4b, 0x61, 0xff, 0xe3, 0x7d, 0x00, 0xb9, 0x40, 0xf6, 0x78, 0x7d, 0x08, 0x1f, 0xeb, 0xec, 0xff, 0x83, 0x0e, 0xc5, 0xe8, 0x55, 0xb3, 0x57, 0xff, 0x09, 0xd3, 0x84, 0x2f, 0xbc, 0x74, 0x2b, 0x0f, 0x89, 0x1b, 0x62, 0xb1, 0x63, 0x26, 0x3e, 0xc7, 0x54, 0xea, 0x54, 0xd3, 0xff, 0x90, 0x6d, 0xf2, 0x98, 0x7c, 0x82, 0xdc, 0xe0, 0x81, 0xd1, 0x1a, 0xb9, 0x89, 0x7f, 0x7b, 0x5f, 0xc8, 0x0c, 0x31, 0x90, 0x4a, 0x1d, 0x26, 0xf3, 0x80, 0x33, 0x21, 0x5e, 0xdf, 0xf8, 0x77, 0x28, 0x44, 0x0e, 0xd0, 0x9b, 0xfa, 0x01, 0x30, 0x35, 0xeb, 0xfd, 0x7c, 0x15, 0xf2, 0x8c, 0x1b, 0x69, 0x20, 0x2b, 0x43, 0x61, 0xd7, 0xeb, 0xe0, 0xb2, 0x24, 0x43, 0x01, 0x8d, 0x82, 0x03, 0xc4, 0xfd, 0x5e, 0x2e, 0xa1, 0xdc, 0xce, 0xb0, 0xf4, 0x05, 0x6d, 0x01, 0x14, 0x1a, 0xb0, 0x6f, 0x54, 0x7d, 0x8f, 0xf1, 0xb5, 0x88, 0xef, 0x58, 0x47, 0x94, 0x29, 0x25, 0x13, 0x89, 0xd6, 0x33, 0xc6, 0xcf, 0x61, 0x48, 0x99, 0x83, 0x10, 0x86, 0x5a, 0xc9, 0x26, 0x49, 0x2e, 0x43, 0x1f, 0x3e, 0xae, 0xc2, 0x60, 0xfc, 0x0a, 0x31, 0x14, 0x6d, 0x68, 0xfe, 0xae, 0xcd, 0x77, 0xc4, 0x68, 0x1f, 0x29, 0x7d, 0x72, 0x68, 0x09, 0x82, 0xfd, 0xe8, 0x05, 0xac, 0x82, 0x25, 0x18, 0x79, 0x48, }, { // nolint: dupl 0x80, 0x60, 0x75, 0x27, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x01, 0xff, 0x93, 0x42, 0xfc, 0x45, 0x80, 0x8c, 0x3d, 0xd0, 0x3f, 0xc4, 0x48, 0x30, 0x7f, 0x40, 0xbd, 0xd8, 0x36, 0x0b, 0xe2, 0x3c, 0x83, 0xad, 0xfa, 0x17, 0xdf, 0x51, 0x1d, 0x83, 0xf9, 0xc9, 0x3b, 0x88, 0x9d, 0x0b, 0x03, 0xf9, 0x2f, 0x27, 0xbc, 0x7e, 0x18, 0xf0, 0x84, 0xf8, 0x53, 0xc2, 0x9e, 0x5f, 0x2c, 0xf8, 0x29, 0xf0, 0x53, 0xe0, 0x86, 0xa4, 0x08, 0xf0, 0x86, 0x4c, 0x95, 0x84, 0x48, 0x7e, 0x98, 0xcc, 0xb5, 0xd6, 0x2b, 0xc1, 0xcd, 0xc0, 0x83, 0x9b, 0x2f, 0x87, 0x2f, 0x8d, 0xa0, 0x2d, 0xe6, 0x08, 0xc6, 0x41, 0x0d, 0x25, 0xec, 0x03, 0xea, 0xdf, 0xa2, 0xfc, 0x10, 0xf8, 0x1d, 0xa4, 0xbc, 0x47, 0x2e, 0x97, 0xbf, 0x92, 0xb1, 0x12, 0x63, 0x49, 0x5f, 0xcb, 0x37, 0x57, 0xa9, 0xbe, 0x4a, 0xc9, 0x3e, 0x0a, 0x04, 0x7b, 0xf7, 0x58, 0xde, 0x51, 0x95, 0x92, 0x6c, 0x47, 0x8d, 0x97, 0x2f, 0x89, 0xec, 0x9e, 0x10, 0xf2, 0xf9, 0x3c, 0x44, 0x6e, 0x10, 0xf0, 0x89, 0x36, 0x02, 0xd6, 0x0b, 0x7c, 0x23, 0x26, 0x5e, 0xb1, 0x75, 0xfa, 0xd4, 0x98, 0x16, 0x65, 0xeb, 0x57, 0x85, 0x3c, 0x29, 0xe0, 0x87, 0xcb, 0x3e, 0x0a, 0x7c, 0x14, 0x96, 0xfd, 0xc8, 0x37, 0x93, 0x26, 0x49, 0xf0, 0xef, 0x88, 0xf0, 0x87, 0x96, 0xeb, 0xdf, 0xeb, 0x52, 0x72, 0xfa, 0x93, 0x2c, 0xb7, 0xf0, 0x87, 0x84, 0x3c, 0x21, 0x26, 0x08, 0x2a, 0xfd, 0x8d, 0xbb, 0x2f, 0x92, 0xb2, 0x44, 0x60, 0x87, 0xc1, 0x04, 0x5d, 0xee, 0xbe, 0x49, 0x30, 0x59, 0xf5, 0xa8, 0xcb, 0xdc, 0x69, 0x7c, 0xbe, 0x5f, 0x27, 0x92, 0xfa, 0xb5, 0x60, 0x40, 0x35, 0xae, 0x4c, 0x93, 0x64, 0x35, 0x01, 0x7e, 0x5b, 0xc2, 0x3e, 0x11, 0xac, 0xa4, 0xf4, 0xbc, 0x11, 0x57, 0xb5, 0x51, 0x2b, 0x11, 0xd7, 0xac, 0x99, 0x7a, 0xd4, 0x1e, 0x6e, 0x06, 0x1f, 0x03, 0x67, 0xc1, 0x0f, 0x82, 0x19, 0x20, 0x69, 0xf0, 0x2f, 0x6b, 0x04, 0x1d, 0x04, 0x3a, 0x2f, 0x01, 0x10, 0x5b, 0xbe, 0x27, 0x1d, 0x5f, 0x2d, 0xa7, 0xbf, 0x93, 0xa7, 0xe0, 0xa7, 0xc0, 0x82, 0x3b, 0xdf, 0x7d, 0xef, 0xa2, 0x54, 0x9e, 0x4f, 0x92, 0x22, 0xf5, 0x97, 0xc9, 0x13, 0x8c, 0x89, 0xc6, 0x9b, 0xdf, 0x89, 0x25, 0x7f, 0x25, 0x61, 0x0f, 0x08, 0x4b, 0x89, 0xef, 0xdc, 0xd5, 0xef, 0x2d, 0xe3, 0x31, 0x3a, 0xac, 0x92, 0x61, 0x4e, 0x07, 0x0f, 0x82, 0x09, 0x60, 0x71, 0xf0, 0x11, 0x99, 0x60, 0x7c, 0xc9, 0xc9, 0x77, 0xfc, 0x33, 0x77, 0x7a, 0xaf, 0xf7, 0x82, 0x81, 0xfb, 0xc5, 0x78, 0xad, 0xef, 0xf8, 0xc7, 0x4b, 0xe4, 0xba, 0x7c, 0xd0, 0xf3, 0xc2, 0x7d, 0x03, 0xe1, 0x3f, 0x97, 0x2a, 0x2d, 0x30, 0x3e, 0x78, 0x13, 0xa2, 0x3a, 0x06, 0x81, 0xe8, 0xd0, 0x3c, 0x1c, 0xdf, 0xb0, 0x16, 0x1f, 0x88, 0xe7, 0xbf, 0x21, 0x2d, 0xc1, 0x2f, 0x67, 0xe9, 0x77, 0x03, 0x84, 0x47, 0x40, 0xec, 0xf6, 0x0f, 0x34, 0x47, 0xa0, 0x7a, 0x5c, 0x10, 0x5e, 0xd4, 0xb5, 0xc0, 0x42, 0x42, 0xfe, 0x52, 0x0d, 0x75, 0xed, 0xfa, 0x81, 0x7a, 0x4e, 0xc1, 0x70, 0x22, 0xdd, 0xff, 0x88, 0xe8, 0x19, 0x47, 0xf6, 0x5c, 0x08, 0xf0, 0xaf, 0xa0, 0x78, 0xc3, 0x2d, 0x6c, 0x2d, 0xbf, 0x8c, 0xf0, 0xf5, 0xf7, 0xe0, 0x7f, 0xbf, 0x7c, 0x1c, 0x5f, 0x60, 0xbe, 0x4f, 0x41, 0x60, 0x65, 0xbf, 0x67, 0x87, 0x62, 0x38, 0xeb, 0x2f, 0x40, 0xf8, 0x08, 0x98, 0x21, 0xf4, 0x0d, 0xb8, 0x4e, 0x6d, 0xbf, 0xc4, 0x71, 0xd2, 0xe1, 0xf7, 0xe0, 0xca, 0x19, 0xf4, 0x0d, 0x52, 0xff, 0xa8, 0x8e, 0xfd, 0xef, 0x88, 0xe6, 0x23, 0xef, 0xbb, 0xe8, 0x3f, 0x8f, 0xf2, 0xc3, 0x77, 0x31, 0xbf, 0xe2, 0x09, 0xd8, 0x2e, 0x04, 0x58, 0x8e, 0x61, 0xef, 0x60, 0xf0, 0x3e, 0x44, 0xf3, 0x9b, 0xdf, 0xc9, 0x7d, 0x9f, 0xcd, 0x67, 0xfc, 0x9d, 0x23, 0x5e, 0x02, 0x16, 0x27, 0xd2, 0xbf, 0x87, 0xe0, 0xab, 0xd9, 0x6c, 0x2b, 0x5f, 0x5c, 0x2d, 0x35, 0xaf, 0xe4, 0xf4, 0x78, 0xc8, 0x8e, 0x92, 0x59, 0x73, 0xa8, 0xcf, 0x63, 0xdb, 0xd7, 0xeb, 0x81, 0x82, 0x23, 0xb0, 0x33, 0x82, 0x07, 0x7e, 0x1f, 0xbe, 0xc0, 0xfe, 0x4f, 0x29, 0x2e, 0x41, 0x1c, 0x69, 0x94, 0x07, 0x90, 0x82, 0x52, 0x2a, 0x0e, 0x3f, 0x2e, 0xc3, 0xf0, 0x3d, 0xcf, 0x5d, 0x3f, 0xf8, 0x43, 0xca, 0x49, 0x9f, 0xa0, 0x5f, 0xdf, 0xb0, 0x7e, 0x2f, 0x40, 0xf4, 0x0f, 0xe1, 0x88, 0xba, 0x1f, 0x67, 0xf0, 0xcc, 0xd7, 0xea, 0x06, 0x99, 0x39, 0x89, 0x70, 0xef, 0xb9, 0x74, 0x17, 0x82, 0x48, 0x4f, 0xd9, 0xb0, 0xbf, 0x84, 0x3b, 0x57, 0x60, 0xf6, 0x7f, 0xcd, 0x6b, 0xf8, 0x4f, 0xde, 0xcf, 0xc0, 0xd1, 0x04, 0x99, 0x81, 0x5b, 0x9f, 0x36, 0xc1, 0xfe, 0x27, 0x7c, 0x83, 0x5f, 0x50, 0xc5, 0x82, 0x63, 0xe8, 0xb5, 0xda, 0xff, 0xf0, 0x9d, 0x65, 0xc4, 0x4d, 0x9c, 0x9f, 0xb8, 0xbd, 0x82, 0xcf, 0x44, 0x68, 0x6d, 0xe3, 0xa2, 0xe8, 0x17, 0x46, 0x52, 0x0f, 0x81, 0x8e, 0x2e, 0xc1, 0x30, 0xb4, 0x17, 0xd4, 0x39, 0x30, 0x33, 0xae, 0x82, 0xff, 0x96, 0x81, 0x67, 0x4f, 0x81, 0x04, 0xf5, 0xdf, 0xfc, 0x14, 0xf8, 0xcf, 0x85, 0x17, 0x3a, 0xa8, 0x03, 0x61, 0xdc, 0x70, 0x30, 0xa0, 0xd1, 0x24, 0xce, 0x4a, 0x8b, 0xba, 0x07, 0xfa, 0xe0, 0x53, 0x89, 0x98, 0x56, 0xd0, 0xe2, 0x0c, 0xe9, 0xd3, 0x95, 0x5e, 0x19, 0xf0, 0xf6, 0xa0, 0x79, 0xf3, 0xf9, 0xe5, 0xf4, 0xcb, 0xd5, 0xfe, 0x5f, 0x40, 0x17, 0x92, 0x2b, 0x2f, 0xde, 0xc0, 0x39, 0xb9, 0xbb, 0x0c, 0x87, 0xb2, 0xc6, 0x74, 0x02, 0x60, 0xf3, 0x80, 0x03, 0x61, 0xe8, 0x72, 0x29, 0xa6, 0x64, 0xfc, 0x31, 0xc3, 0xc8, 0x81, 0xe9, 0xa1, 0x86, 0x93, 0xfc, 0x46, 0x60, 0x3b, 0x03, 0xd6, 0x9f, 0xfe, 0x10, 0xf2, 0xa0, 0x7d, 0xb8, 0x30, 0x39, 0xde, 0xc1, 0x68, 0xbc, 0xa2, 0xf0, 0x76, 0x8c, 0xe1, 0x82, 0x36, 0x47, 0x49, 0x20, 0x82, 0x32, 0xcc, 0xc3, 0xd1, 0x9c, 0xa0, 0x80, 0x2c, 0x56, 0x5c, 0x24, 0x2d, 0x08, 0x81, 0xc6, 0x50, 0x63, 0x49, 0x3e, 0x33, 0xa0, 0xfa, 0x04, 0x70, 0x5b, 0x1f, 0xd1, 0x95, 0x4d, 0x05, 0xe1, 0x1e, 0x12, 0x3b, 0xf6, 0x9e, 0x4a, 0x40, 0xc0, 0x94, 0x31, 0x72, 0xfe, 0x30, 0x78, 0xc1, 0x78, 0xfe, 0xc0, 0xed, 0xf7, 0x46, 0x72, 0x0b, 0x43, 0x9c, 0x23, 0xd1, 0x39, 0x64, 0xf7, 0xe3, 0x09, 0xee, 0x4a, 0x2c, 0x4c, 0x38, 0x08, 0x3a, 0xde, 0x4b, 0x81, 0x83, 0xc3, 0xe4, 0x27, 0xb0, 0x0a, 0x96, 0x6d, 0xfd, 0x4b, 0x16, 0x0c, 0x68, 0xaa, 0x41, 0x78, 0xdf, 0x24, 0x6f, 0x98, 0x45, 0xde, 0x98, 0x71, 0x4c, 0x8e, 0x06, 0xe3, 0x02, 0x59, 0x2d, 0x8c, 0xdd, 0x6d, 0xff, 0xb0, 0xa1, 0x2c, 0x7f, 0x89, 0xf8, 0x47, 0xa7, 0x90, 0x21, 0xa0, 0xbb, 0x05, 0xe4, 0x5e, 0xf2, 0x8b, 0xf6, 0x2e, 0x61, 0x2d, 0x02, 0x8f, 0x05, 0x15, 0xc3, 0x50, 0xd2, 0x7f, 0x8b, 0xcd, 0xf5, 0xfb, 0x7e, 0xee, 0x51, 0x8f, 0xe0, 0x46, 0x9b, 0xa0, 0xb8, 0x7e, 0x5e, 0x81, 0xf3, 0x4b, 0xd0, 0x3e, 0x1a, 0x9b, 0x90, 0x10, 0x37, 0x0b, 0xcd, 0xec, 0xf0, 0x3f, 0x7e, 0x5e, 0x8f, 0xe5, 0xee, 0x8d, 0x47, 0xcd, 0xda, 0xa8, 0x4b, 0xc3, 0x52, 0xfb, 0xe2, 0x26, 0xe8, 0x29, 0x60, 0x4b, 0xdc, 0x21, 0xea, 0x5e, 0x97, 0xcd, 0xe5, 0x24, 0xb2, 0x0b, 0xed, 0x7a, 0x07, 0xc6, 0x8b, 0xe5, 0xc1, 0xdf, 0x6a, 0x9a, 0xa2, 0x65, 0x9f, 0x26, 0x85, 0xee, 0x22, 0xc2, 0x6b, 0x0f, 0x38, 0x7a, 0xf8, 0x8b, 0x48, 0xff, 0x1b, 0x20, 0xff, 0x05, 0x34, 0x4c, 0xc2, 0xfb, 0x06, 0xd7, 0x5e, 0x2f, 0x8f, 0xc6, 0x4a, 0x7f, 0x41, 0x03, 0xfa, 0x0b, 0xe3, 0xe8, 0x5f, 0x07, 0xec, 0x2f, 0x8e, 0x98, 0xa5, 0x29, 0xc1, 0x5e, 0xf4, 0xbe, 0x3e, 0xc5, 0x0f, 0x38, 0x7c, 0xd1, 0xc4, 0xd3, 0xf6, 0x7f, 0x11, 0x81, 0xf2, 0x92, 0x4f, 0x02, 0xba, 0x5a, 0xa0, 0x72, 0x2f, 0xa5, 0xd2, 0xe4, 0x29, 0x79, 0x49, 0x71, 0x3e, 0xfc, 0x3e, 0x52, 0xf4, 0x0f, 0xa0, 0xa9, 0x74, 0x05, 0xf2, 0x7c, 0xba, 0xeb, 0x1b, 0xe5, 0xbc, 0xa6, 0xe9, 0x56, 0x42, 0xf5, 0xef, 0xdf, 0x9b, 0x59, 0x2f, 0x11, 0xd6, 0xaf, 0x57, 0xaa, 0x93, 0xb0, 0x4c, 0xf3, 0x4d, 0xa3, 0xfd, 0xf9, 0x05, 0x75, 0x11, 0xb7, 0x4e, 0xc5, 0x32, 0xfb, 0x0a, 0x78, 0x29, 0xf8, 0xbd, 0x0f, 0xb7, 0xf8, 0xbf, 0x1f, 0x0d, 0x67, 0x3f, 0xa0, 0xcc, 0x5b, 0xc5, 0xfb, 0x10, 0xd8, 0xe5, 0xff, 0x84, 0xf6, 0x7f, 0x0d, 0x33, 0x7e, 0x1a, 0x21, 0x69, 0x52, 0x21, 0x7d, 0x7b, 0x85, 0xbb, 0x0c, 0x78, 0x37, 0x7b, 0x6b, 0xb7, 0xfd, 0x44, 0xd8, 0x5f, 0x3e, 0x70, 0x21, 0x49, 0x65, 0xf7, 0x7b, 0x34, 0x0f, 0x92, 0x1d, 0xf4, 0x0f, 0x41, 0x71, 0xb4, 0xa5, 0xe5, 0xfe, 0x56, 0xbf, 0xee, 0x3f, 0xa0, 0x7a, 0x2a, 0x07, 0x9c, 0x21, 0xf8, 0xbf, 0x6b, 0x30, 0xdf, 0x01, 0x19, 0x11, 0xe7, 0x1e, 0xd2, 0xf8, 0x2e, 0xda, 0xb0, 0x17, 0x45, 0xaf, 0x9b, 0x8c, 0xd5, 0x40, 0x5a, 0x8e, 0xa0, 0xa7, 0xc0, 0xee, 0x9b, 0xaf, 0xe5, 0x5f, 0x0a, 0xf9, 0xe4, 0xf4, 0x0f, 0x71, 0x5d, 0x34, 0x0b, 0x22, 0x7f, 0x7e, 0x80, 0x5e, 0x13, 0xf2, 0x92, 0xd0, 0x1f, 0xc4, 0x79, 0xa1, 0xd8, 0x1f, 0x8d, 0xe3, 0x41, 0x7b, 0x95, 0x1a, 0x6f, 0x18, 0x2f, 0x07, 0xfa, 0x0a, 0x11, 0x38, 0x75, 0x01, 0x76, 0xbf, 0xb8, 0xde, 0x3a, 0x16, 0x60, 0x3c, 0xc3, 0x12, 0x97, 0x4b, 0x8f, 0x4c, 0x9b, 0xbf, 0x30, 0x28, 0x6e, 0x93, 0xdc, 0x3d, 0x65, 0x6b, 0xa0, 0x7f, 0xf1, 0xbc, 0xc0, 0x81, 0x6e, 0x8d, 0x1e, 0x1c, 0x74, 0xb2, 0xd8, 0x69, 0x0e, 0xb4, 0x91, 0xde, 0x01, 0x69, 0xae, 0x8d, 0xbf, 0xd4, 0x6e, 0xd5, 0x80, 0xb7, 0x41, 0x20, 0x24, 0xf8, 0x07, 0x94, 0xc9, 0xbf, 0xdd, 0x24, 0x17, 0xee, 0x32, 0x9c, 0xe4, 0x12, 0x82, 0xd1, 0xde, 0x62, 0x3e, 0xf5, 0x8e, 0xb9, 0x47, 0xa7, 0x5c, 0x1a, 0xa0, 0x0b, 0x51, 0xbd, 0x58, 0x58, 0x57, 0x3f, 0x38, 0x63, 0x03, 0x4f, 0x43, 0x40, 0xc2, 0x3c, 0x33, 0xea, 0x8e, 0x10, 0x7b, 0xb0, 0xf1, 0x02, 0xbb, 0xfa, 0x7e, 0x37, 0xc0, 0xbb, 0xa4, 0xdd, 0x5d, 0x14, 0xf0, 0xe6, 0x4b, 0x40, 0xa0, 0xc3, 0xb5, 0xd7, }, { // nolint: dupl 0x80, 0x60, 0x75, 0x28, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x01, 0xfe, 0x2a, 0xd7, 0xe5, 0x4f, 0xc1, 0x27, 0x66, 0x86, 0xdf, 0x35, 0x29, 0x2f, 0xc8, 0x1c, 0xe6, 0x46, 0x1c, 0xb2, 0x2f, 0x31, 0x9f, 0xd4, 0x11, 0xfd, 0xfa, 0xb2, 0x9b, 0xcb, 0x0f, 0x77, 0xea, 0xe1, 0x3d, 0xf3, 0xcd, 0x23, 0x8c, 0x57, 0x3e, 0xb5, 0x09, 0xdf, 0xbf, 0xd9, 0x45, 0xfb, 0xe0, 0x57, 0xbb, 0xa3, 0x79, 0x3b, 0x27, 0x0d, 0x7e, 0x5e, 0xbd, 0x42, 0x56, 0x5f, 0xbf, 0x20, 0x23, 0xe8, 0x36, 0x0f, 0x2e, 0xe1, 0x0e, 0xc0, 0xba, 0x01, 0xfb, 0x2c, 0x92, 0xfb, 0x58, 0x1c, 0x65, 0xe3, 0x9f, 0xc9, 0xc5, 0xf9, 0x8b, 0xe6, 0x1e, 0x39, 0x8f, 0x9b, 0xd8, 0x2f, 0x25, 0x83, 0xeb, 0x9e, 0xa8, 0x2f, 0xfa, 0x84, 0xa8, 0x1f, 0xd2, 0xe1, 0xc8, 0x89, 0x08, 0x7d, 0x7c, 0xff, 0x84, 0xf6, 0x74, 0xba, 0x58, 0x21, 0xba, 0x39, 0x09, 0x7c, 0x10, 0xd0, 0x1a, 0x0b, 0x73, 0x92, 0x23, 0x60, 0x2d, 0x02, 0xfb, 0xbb, 0x06, 0xff, 0x8b, 0xe1, 0xd5, 0xf1, 0xbc, 0xd5, 0x84, 0x9f, 0xc4, 0xf4, 0x0f, 0x8d, 0xe7, 0xf8, 0x42, 0xc1, 0xfc, 0xec, 0x10, 0x38, 0x52, 0x09, 0x0b, 0xd3, 0x5d, 0x0f, 0x9a, 0x2e, 0x1a, 0xd4, 0xfc, 0x01, 0x69, 0x0a, 0x26, 0x05, 0x03, 0xfe, 0x4c, 0xc3, 0x1f, 0x71, 0x94, 0x0b, 0xf3, 0x4a, 0xd1, 0xc4, 0x06, 0xd8, 0x2a, 0x17, 0xb8, 0x53, 0x47, 0xd8, 0x5e, 0xc3, 0xe8, 0xe4, 0x6e, 0x58, 0x2b, 0xf4, 0x13, 0x08, 0xc3, 0xb2, 0x8f, 0xb3, 0x4a, 0x83, 0xd5, 0x70, 0x53, 0x4b, 0x63, 0xe6, 0x5d, 0x97, 0xdb, 0xaa, 0xeb, 0xad, 0x70, 0xf7, 0x05, 0x40, 0x28, 0x6a, 0x1a, 0xe1, 0x97, 0xd6, 0xe9, 0x6f, 0xe0, 0xda, 0xff, 0xc7, 0xf1, 0x88, 0xc1, 0xa2, 0x4b, 0x81, 0x66, 0x6e, 0x4b, 0x18, 0x23, 0xd0, 0x34, 0x4c, 0x5e, 0x80, 0xbd, 0xc3, 0xb8, 0x69, 0x2e, 0x83, 0xed, 0x48, 0xe9, 0x84, 0x99, 0x3b, 0x54, 0xde, 0x2d, 0xc5, 0xaa, 0x69, 0x7f, 0xe1, 0x9e, 0xa3, 0xb7, 0x90, 0xd5, 0x2f, 0xfe, 0x0b, 0x23, 0xa5, 0xeb, 0x54, 0xa2, 0x43, 0x54, 0xbd, 0x93, 0x17, 0x8a, 0x26, 0x0d, 0x82, 0xd7, 0x71, 0xba, 0x04, 0x38, 0x3e, 0xcb, 0x40, 0xb3, 0x27, 0x6a, 0x97, 0x86, 0x0c, 0x07, 0xe8, 0x1c, 0x70, 0xdc, 0xe2, 0x0c, 0x54, 0xd6, 0x97, 0xfe, 0x33, 0xb0, 0xd8, 0x9b, 0x3a, 0x6c, 0x26, 0x84, 0x25, 0xc0, 0xbd, 0x9d, 0x29, 0x49, 0x78, 0xc8, 0x3e, 0x21, 0x9b, 0x61, 0xfb, 0x58, 0x2c, 0x78, 0x1e, 0xbd, 0xf8, 0x02, 0xb0, 0xbe, 0x23, 0x04, 0xe0, 0xc8, 0x84, 0x4a, 0x80, 0x8a, 0x05, 0x40, 0x18, 0x0c, 0xa0, 0x83, 0xe3, 0x7a, 0x48, 0x32, 0x25, 0x63, 0xd8, 0x1b, 0x2f, 0x49, 0x86, 0x92, 0xe9, 0xff, 0xc6, 0xda, 0x06, 0x30, 0xe7, 0x6e, 0xb4, 0x22, 0x94, 0x08, 0xa1, 0xbd, 0xe3, 0xed, 0x34, 0x17, 0x5d, 0x7f, 0xe3, 0xe1, 0xdb, 0xb9, 0x3e, 0x2c, 0xd1, 0x06, 0x87, 0x44, 0x11, 0xf0, 0xed, 0xd8, 0xb7, 0x05, 0x7e, 0xc4, 0xb6, 0x0f, 0xc4, 0xd0, 0x32, 0xb5, 0xa1, 0xbb, 0xa1, 0x3b, 0x07, 0xd4, 0x9d, 0x8b, 0xdc, 0xbb, 0x02, 0xf0, 0xf7, 0x85, 0xbf, 0x15, 0xab, 0xca, 0x1a, 0x9c, 0x20, 0x15, 0xd4, 0x76, 0x75, 0x55, 0x6a, 0x52, 0x68, 0x1e, 0xff, 0x0f, 0x50, 0x3b, 0x07, 0xa0, 0x5b, 0x3e, 0xf6, 0xff, 0xee, 0x08, 0x6c, 0x76, 0x0a, 0xce, 0xa3, 0x69, 0x3c, 0xf0, 0xb4, 0x30, 0x4d, 0x66, 0x8c, 0x1e, 0xbc, 0xe1, 0x00, 0xca, 0x9f, 0x29, 0xbc, 0x08, 0xd8, 0x52, 0x53, 0xf5, 0x8e, 0x63, 0xfc, 0x6f, 0x1b, 0x1e, 0xf4, 0x38, 0x07, 0xed, 0x95, 0x74, 0x92, 0x59, 0x54, 0x10, 0x5c, 0xd1, 0xb2, 0x40, 0xcf, 0x2b, 0x84, 0x0f, 0xfa, 0xf4, 0x24, 0x0b, 0xfc, 0x6d, 0x80, 0x02, 0x73, 0x18, 0x4e, 0x46, 0xe2, 0xfc, 0x03, 0xa2, 0x30, 0xab, 0x35, 0x30, 0x6f, 0x65, 0xc8, 0x25, 0x68, 0xc8, 0xbe, 0x3d, 0x3a, 0x12, 0xc5, 0xed, 0x7f, 0x8d, 0x90, 0x3e, 0x34, 0xd8, 0xb0, 0x53, 0x3c, 0x31, 0xd9, 0x47, 0x41, 0x40, 0xd1, 0xc7, 0x6e, 0x2c, 0x6d, 0xe2, 0x33, 0xd2, 0x35, 0x9c, 0x41, 0xf7, 0x72, 0xf5, 0xd8, 0x7e, 0x8b, 0xc6, 0xc2, 0x67, 0x19, 0xe1, 0xd9, 0xde, 0x82, 0x81, 0xca, 0x1a, 0x2a, 0x56, 0x08, 0xb9, 0x35, 0xf6, 0x12, 0x2e, 0x91, 0x46, 0x63, 0xdf, 0xe3, 0x6c, 0x09, 0x58, 0x6d, 0x5f, 0xaa, 0x26, 0xbe, 0x95, 0xe2, 0x75, 0x34, 0x49, 0xde, 0xc3, 0xfa, 0x35, 0x6f, 0xa0, 0x67, 0xdd, 0x76, 0x1f, 0xf9, 0x46, 0xe8, 0x6f, 0xe9, 0x2f, 0xa6, 0x3c, 0x08, 0x16, 0xe0, 0x02, 0xf8, 0xd6, 0xdb, 0x34, 0x38, 0x05, 0x60, 0x37, 0x9e, 0x2a, 0x7a, 0x7d, 0xbf, 0x36, 0x8a, 0xfe, 0x2b, 0x62, 0x61, 0x30, 0xed, 0xfe, 0x32, 0x80, 0xd3, 0x1d, 0x24, 0xed, 0x0f, 0x9c, 0x28, 0x87, 0xe3, 0xf7, 0x4a, 0x43, 0xbe, 0xa6, 0x3f, 0x40, 0xfc, 0x55, 0x87, 0x08, 0x3a, 0x88, 0x85, 0x42, 0x9b, 0x80, 0xff, 0x0f, 0x43, 0xc9, 0x5e, 0xd7, 0x85, 0x0e, 0xa9, 0xf4, 0x15, 0x88, 0x81, 0xa3, 0x86, 0xda, 0x3b, 0x49, 0x53, 0xff, 0xb8, 0xc9, 0x6d, 0x03, 0xcf, 0xb1, 0xe7, 0x82, 0x6e, 0x08, 0x04, 0xe0, 0x0c, 0x05, 0xd8, 0x13, 0xcc, 0x7f, 0x0a, 0x70, 0xda, 0x57, 0x77, 0x86, 0x72, 0x8f, 0xa1, 0x59, 0x50, 0xf8, 0xe8, 0x14, 0x18, 0x0b, 0xb9, 0xf1, 0x99, 0xc1, 0x65, 0x5f, 0x60, 0xe7, 0x44, 0x73, 0xaf, 0x06, 0x10, 0x7f, 0xa1, 0x7e, 0xe1, 0x08, 0x4a, 0xd7, 0x4e, 0x96, 0x8c, 0xb0, 0x62, 0x51, 0xca, 0x0b, 0x43, 0xfa, 0x8f, 0xf5, 0x41, 0xec, 0x3e, 0xc7, 0xf1, 0x7e, 0xc1, 0xe5, 0x06, 0x7c, 0x66, 0x36, 0x2e, 0xed, 0x08, 0x0f, 0x00, 0x8a, 0x10, 0x39, 0xd7, 0xa0, 0xf4, 0x0e, 0x8b, 0xf1, 0x90, 0xdc, 0xbb, 0xd4, 0x92, 0x60, 0x96, 0x01, 0xee, 0xdb, 0x47, 0x5a, 0xa0, 0xa1, 0x05, 0x8c, 0x6a, 0x36, 0xac, 0x7c, 0x7f, 0xc6, 0x51, 0xbd, 0x3c, 0xc4, 0x90, 0x98, 0x10, 0x17, 0x8c, 0x23, 0x60, 0x55, 0xf5, 0x47, 0xdc, 0x64, 0xa0, 0xba, 0x36, 0x18, 0xe8, 0xa1, 0xf5, 0x39, 0x70, 0xb7, 0xaf, 0x35, 0xfa, 0x8c, 0xa0, 0x6c, 0x1a, 0xb1, 0x2e, 0x93, 0xec, 0x56, 0x53, 0x5b, 0x00, 0xfd, 0xc6, 0x66, 0x40, 0x8d, 0x74, 0x20, 0xe5, 0xc5, 0x41, 0xd0, 0x1a, 0x15, 0x58, 0x2f, 0x74, 0x0b, 0xd9, 0x50, 0xbf, 0x8e, 0xd8, 0x26, 0x04, 0xc3, 0xca, 0xaa, 0x74, 0x4b, 0xc3, 0x01, 0x29, 0xdf, 0xc3, 0x5a, 0x1c, 0x88, 0x17, 0xbf, 0xee, 0x26, 0x9d, 0x9f, 0x95, 0x8f, 0x86, 0xa3, 0x80, 0xaf, 0xeb, 0xd8, 0x5f, 0xb8, 0xca, 0x06, 0x51, 0xe3, 0x02, 0xfc, 0x21, 0xe4, 0xb4, 0x41, 0x6e, 0x64, 0x60, 0x40, 0x8a, 0x83, 0xe1, 0xe5, 0x13, 0xfe, 0xa3, 0x2d, 0x59, 0x0f, 0x80, 0x48, 0x3c, 0x74, 0x2f, 0xc0, 0x25, 0x67, 0x86, 0x02, 0x3d, 0x69, 0x26, 0x2f, 0xe3, 0x3c, 0xfa, 0x38, 0x16, 0xa8, 0x28, 0x1b, 0x09, 0x38, 0x07, 0x61, 0x1c, 0x10, 0x5e, 0x31, 0xfe, 0x3a, 0xc1, 0x20, 0x73, 0x8f, 0xe3, 0xc0, 0xbd, 0x04, 0x40, 0x54, 0x05, 0xf8, 0x5f, 0x18, 0x3c, 0x55, 0xa2, 0xde, 0xf9, 0xce, 0x2d, 0x97, 0xfe, 0x11, 0xd0, 0x95, 0x00, 0xf3, 0x83, 0x18, 0x0b, 0x60, 0x88, 0x33, 0xa8, 0xdd, 0x81, 0xb1, 0xcb, 0x0d, 0x07, 0xd8, 0xac, 0x10, 0xd9, 0x73, 0xd5, 0xb4, 0xff, 0xf1, 0x9d, 0x33, 0x84, 0x33, 0x8c, 0xd8, 0x07, 0x0e, 0xae, 0xe7, 0xc1, 0x10, 0x31, 0x7f, 0xc6, 0xe4, 0x17, 0x1d, 0x07, 0x96, 0x86, 0xb2, 0x9d, 0x2c, 0x9e, 0xa2, 0x80, 0xf5, 0x93, 0x0a, 0xe3, 0xf8, 0x0d, 0x7a, 0x3f, 0xd4, 0x6d, 0x83, 0xd1, 0xb0, 0x51, 0xc1, 0xd7, 0xe3, 0xed, 0xf5, 0xa7, 0xfe, 0xe3, 0x38, 0xf8, 0x9e, 0xdf, 0xdb, 0x38, 0x61, 0x86, 0xf6, 0xbe, 0xe3, 0x75, 0x3f, 0xc6, 0x71, 0xb8, 0x04, 0x0f, 0x50, 0x8f, 0x3d, 0x85, 0x00, 0x66, 0x55, 0xf4, 0x0b, 0xf1, 0x9d, 0xeb, 0x10, 0x1b, 0xce, 0x8f, 0x0f, 0x40, 0x5e, 0xbc, 0x1b, 0x4a, 0xb7, 0x5f, 0x8c, 0xe3, 0xce, 0xb7, 0xde, 0x70, 0xc8, 0xd9, 0xf5, 0x54, 0x2e, 0x2e, 0xc2, 0xe4, 0x13, 0xd6, 0x8d, 0x8d, 0x58, 0x97, 0xd5, 0xdc, 0xdc, 0xa5, 0xfe, 0x15, 0xdf, 0xbf, 0x96, 0x73, 0x57, 0xfe, 0x53, 0xf0, 0xfa, 0x7f, 0xc6, 0xdf, 0xf4, 0x1b, 0xd3, 0xd4, 0x76, 0x55, 0x1e, 0x2c, 0xe4, 0xcd, 0xb4, 0x64, 0x53, 0x74, 0xfa, 0x7e, 0x51, 0xbd, 0x01, 0x67, 0x5c, 0xea, 0x22, 0xa2, 0xb0, 0x19, 0x98, 0x64, 0x6b, 0x30, 0x6d, 0x7d, 0x7f, 0x8d, 0x8f, 0xc2, 0x31, 0x3d, 0x5c, 0x6c, 0x2f, 0x81, 0x53, 0xab, 0x9c, 0xba, 0x38, 0x24, 0xa8, 0x97, 0x61, 0xa7, 0xfe, 0x37, 0x04, 0xf4, 0xae, 0xd5, 0xf2, 0x86, 0xb4, 0x0b, 0x63, 0x40, 0xd0, 0x7e, 0x2f, 0xbf, 0xdc, 0x29, 0xe8, 0x33, 0x1c, 0xec, 0x1a, 0x7d, 0x07, 0xb0, 0x14, 0xba, 0x82, 0x0e, 0x0e, 0xe4, 0x9c, 0xa3, 0xec, 0x15, 0x43, 0xb1, 0xaa, 0x0b, 0xa2, 0xb8, 0x2d, 0x40, 0x6e, 0x65, 0x4a, 0x43, 0xb7, 0xfc, 0x81, 0x49, 0x85, 0x3c, 0x85, 0xbf, 0x20, 0x7a, 0x80, 0xa3, 0xe2, 0x43, 0xcb, 0xb2, 0x07, 0x7d, 0x30, 0x7e, 0x1a, 0xc5, 0xee, 0xb5, 0xff, 0xc2, 0xde, 0x80, 0xa7, 0x66, 0xea, 0xff, 0xfc, 0x5e, 0xbf, 0x72, 0x54, 0x59, 0xb3, 0xa9, 0x65, 0x98, 0x10, 0x1e, 0x2f, 0xc1, 0x2f, 0xe5, 0xa5, 0x9a, 0x18, 0xc8, 0xbf, 0x65, 0xd1, 0xe0, 0x41, 0xac, 0x5c, 0x14, 0xc5, 0xf8, 0xe0, 0xd5, 0xd5, 0xcb, 0x17, 0xda, 0xe8, 0xfe, 0x6e, 0xed, 0x60, 0x66, 0x84, 0xaf, 0xe9, 0x7e, 0x18, 0xeb, 0xa3, 0xd7, 0x4f, 0xf9, 0xa1, 0x2b, 0xfe, 0xcf, 0x72, 0xd8, 0x3b, 0x5c, 0x0c, 0x7f, 0x8b, 0xec, 0x24, 0xf7, 0xe1, 0x98, 0xba, 0x05, 0xd8, 0x3f, 0x81, 0x56, 0x18, 0xe3, 0x05, 0x9c, 0xe9, 0x30, 0x6b, 0xdb, 0xfc, 0xde, 0x1e, 0x8b, 0xef, 0xdf, 0xe6, 0xf3, 0xc3, 0xcb, 0xd1, 0x35, 0xb8, 0xbe, 0xf4, 0xbe, 0xeb, 0x5c, 0x6c, 0x98, 0xa1, 0xf9, 0xa2, 0xb0, 0x0c, 0x68, 0xbb, 0xef, 0xfc, 0x66, 0x83, 0xa0, 0xf6, 0x0b, 0xf7, 0xed, 0x60, 0x43, 0x97, 0x40, 0xff, 0x13, 0x66, 0xc1, 0xfa, 0x0b, 0xe5, 0xf6, 0x07, 0x84, 0x7f, 0x37, 0x60, 0xf8, 0x1b, 0x26, 0xec, 0x17, 0xc5, 0xf6, 0x0f, 0xd1, 0x60, 0xfe, 0x6b, 0x37, 0xfc, 0xbd, 0xf8, 0x27, 0xf0, 0x31, 0x4b, }, { // nolint: dupl 0x80, 0x60, 0x75, 0x29, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x01, 0xd1, 0xf0, 0xcc, 0xbd, 0x03, 0xee, 0x6e, 0x97, 0xc5, 0xf4, 0x0b, 0xd8, 0x3c, 0x04, 0x2c, 0xdd, 0x1f, 0x51, 0x7e, 0xc1, 0x6c, 0x19, 0x8b, 0xf9, 0x7b, 0x0b, 0x81, 0xff, 0xc0, 0x44, 0xfe, 0x6e, 0xc1, 0x23, 0xf3, 0x72, 0x98, 0xf1, 0x33, 0x7a, 0x07, 0xa9, 0x7d, 0x03, 0xf3, 0x76, 0x0e, 0xf9, 0xbb, 0x34, 0x7a, 0x8b, 0xe6, 0x1e, 0xf2, 0x0c, 0xae, 0x6e, 0x81, 0xb3, 0xcb, 0x37, 0x40, 0xbe, 0x5e, 0xc1, 0xd4, 0x49, 0x45, 0xf2, 0xe1, 0x73, 0x19, 0x64, 0xdc, 0xc2, 0x12, 0xf4, 0x0e, 0xb9, 0xbb, 0x57, 0x2c, 0xdc, 0x77, 0xda, 0xe5, 0xf6, 0x7a, 0x28, 0xbe, 0x32, 0xc9, 0xf4, 0xc7, 0x7d, 0x7e, 0x23, 0xa0, 0xb8, 0x17, 0xc6, 0xfa, 0xfc, 0x55, 0x1d, 0x07, 0x40, 0xf4, 0x0b, 0xa2, 0xf3, 0x44, 0x7b, 0x07, 0x1a, 0x18, 0xfe, 0xe6, 0xde, 0x52, 0x5c, 0xbd, 0x7a, 0xd7, 0x1b, 0x9d, 0x8a, 0x09, 0x78, 0x57, 0x63, 0x7c, 0xe1, 0x94, 0xe8, 0x45, 0x1c, 0x28, 0xc5, 0x7c, 0x41, 0xae, 0x03, 0xca, 0xbd, 0x7f, 0x14, 0x41, 0xb4, 0x13, 0x90, 0xb2, 0xe1, 0xd6, 0x8c, 0x68, 0x07, 0xce, 0x8f, 0xc3, 0x5c, 0x08, 0x56, 0xc7, 0x0b, 0x58, 0x71, 0x8d, 0xa7, 0x83, 0xdd, 0x9f, 0x50, 0xc7, 0xeb, 0xf8, 0x52, 0xdc, 0xb5, 0xe7, 0xcc, 0x0d, 0xd9, 0xf9, 0x55, 0xb1, 0x0c, 0x97, 0x69, 0x50, 0xde, 0x96, 0x06, 0x3f, 0x80, 0x69, 0x9d, 0x3e, 0x36, 0xde, 0x18, 0xcb, 0x50, 0xad, 0x31, 0xe2, 0x6c, 0xc1, 0x83, 0xf6, 0xd7, 0x5f, 0xf8, 0xcb, 0xe3, 0x45, 0xca, 0x3d, 0x01, 0x18, 0xb2, 0x5d, 0x92, 0x37, 0x06, 0x48, 0x3b, 0x47, 0x82, 0x88, 0xde, 0x38, 0x14, 0xe8, 0x53, 0xea, 0x35, 0x5d, 0xa3, 0x41, 0x20, 0x78, 0x10, 0xd3, 0x45, 0xaf, 0xed, 0xf2, 0x46, 0xd2, 0xd4, 0x82, 0x1f, 0xbc, 0x75, 0xc8, 0xcd, 0xc3, 0xe2, 0x36, 0x07, 0x29, 0xb1, 0x68, 0x62, 0x08, 0x8a, 0xd3, 0xff, 0x51, 0xb6, 0xf4, 0x11, 0xc3, 0x02, 0x95, 0x51, 0x16, 0x06, 0xf2, 0x18, 0x04, 0xb9, 0x8f, 0x76, 0x03, 0x1d, 0x01, 0x95, 0xfc, 0xc9, 0x3b, 0x8d, 0xe8, 0x19, 0x9d, 0x46, 0x80, 0xc6, 0x22, 0x94, 0x31, 0xa6, 0xf3, 0xd8, 0x7a, 0x2d, 0x07, 0x40, 0xa8, 0x0d, 0x82, 0x5f, 0xd9, 0x7c, 0x6f, 0xa2, 0x21, 0x8e, 0xd5, 0x95, 0x11, 0x92, 0x32, 0xac, 0x90, 0x94, 0x0d, 0xd4, 0x5d, 0xb5, 0xfe, 0xe1, 0x4b, 0x6e, 0x06, 0x2b, 0x0b, 0x83, 0xc7, 0x1f, 0x5d, 0x1d, 0x92, 0x2b, 0xb0, 0x11, 0x19, 0x73, 0xe1, 0x0d, 0xc6, 0x49, 0xd0, 0x05, 0xb0, 0x1a, 0x0c, 0x74, 0x9d, 0xd0, 0xfb, 0x85, 0x3b, 0x02, 0x20, 0xc5, 0x6b, 0x04, 0x9f, 0x9c, 0xaf, 0x86, 0xf3, 0x06, 0x0f, 0xba, 0x85, 0x3c, 0xa6, 0x5a, 0xaf, 0x0d, 0x07, 0x40, 0x3b, 0x43, 0x60, 0x38, 0x28, 0x45, 0xde, 0xd1, 0x6c, 0x75, 0x19, 0xec, 0x2e, 0xcd, 0x80, 0x82, 0x7c, 0xff, 0x91, 0x4d, 0x6e, 0x6f, 0x22, 0x2f, 0x71, 0xba, 0x94, 0x40, 0xf9, 0x8c, 0xf8, 0xcb, 0xa4, 0xab, 0xec, 0xc4, 0x1e, 0x7e, 0x70, 0x7f, 0xae, 0x05, 0xa8, 0x5e, 0xd3, 0x5b, 0xf5, 0xd3, 0x4f, 0xf0, 0x77, 0x5f, 0x7c, 0x25, 0xd3, 0xa5, 0xa5, 0xb2, 0x9b, 0xd2, 0xc0, 0xc9, 0x27, 0x5e, 0x3e, 0x4e, 0xbc, 0x1a, 0xc3, 0x5d, 0x6b, 0xfd, 0xf1, 0xd2, 0x7b, 0xee, 0xeb, 0xf0, 0x31, 0x4d, 0xdf, 0x83, 0x92, 0x82, 0x8d, 0x7d, 0xaf, 0x70, 0x94, 0xf5, 0xe9, 0xff, 0x08, 0x06, 0x6b, 0xf5, 0xa7, 0xfe, 0xa1, 0xce, 0x9d, 0x6b, 0xfe, 0xe0, 0x9f, 0xd7, 0xbe, 0x7e, 0x88, 0x1c, 0xf6, 0xdb, 0x7f, 0xf6, 0x55, 0x31, 0x70, 0x2a, 0x10, 0x13, 0x7a, 0xd1, 0xf5, 0xf0, 0x49, 0x45, 0xe5, 0xc3, 0x64, 0x52, 0x83, 0x86, 0x2f, 0x9f, 0x3a, 0x27, 0x02, 0x84, 0x39, 0xb5, 0xae, 0xdf, 0xf9, 0x3e, 0x5d, 0x3f, 0x04, 0x35, 0x83, 0x87, 0xbc, 0x04, 0xe5, 0x75, 0xf3, 0xf5, 0x36, 0xff, 0xf0, 0x49, 0x6f, 0xcb, 0xa9, 0xeb, 0xb2, 0xfa, 0x81, 0x67, 0xf3, 0xfb, 0x4f, 0xf5, 0x0f, 0x57, 0x2e, 0xe7, 0xae, 0xd3, 0x2f, 0x24, 0x7d, 0x4b, 0x77, 0xea, 0xef, 0xd7, 0x74, 0xbf, 0xba, 0x7d, 0xc0, 0x81, 0x26, 0x97, 0x81, 0x8e, 0x4b, 0x09, 0x75, 0x0f, 0xef, 0xae, 0x5d, 0xde, 0x42, 0x5e, 0xe4, 0xd2, 0xf7, 0x5b, 0xd5, 0x45, 0x64, 0x25, 0xbf, 0xdf, 0xb2, 0x77, 0xae, 0x22, 0xc4, 0x78, 0xf1, 0xfe, 0x02, 0x0a, 0x23, 0xb3, 0xf1, 0xe3, 0x7a, 0x07, 0x02, 0xdc, 0x46, 0x1f, 0xad, 0x03, 0xf8, 0xff, 0x60, 0x23, 0x18, 0xae, 0x30, 0xf2, 0x0b, 0xc6, 0x3c, 0xa2, 0x1d, 0xc4, 0x7a, 0xed, 0xfa, 0x8e, 0x9f, 0x3f, 0x5d, 0x9f, 0xcd, 0x04, 0x54, 0xb9, 0x33, 0xbb, 0x88, 0xaf, 0x2d, 0x3f, 0xe4, 0x18, 0xe0, 0x71, 0x87, 0xac, 0x99, 0xf4, 0xae, 0xdd, 0x02, 0xd7, 0xd3, 0xfa, 0x85, 0x7a, 0x3a, 0xbd, 0xba, 0x9b, 0x04, 0xad, 0xff, 0xd4, 0x45, 0x0a, 0x6b, 0xab, 0x50, 0xf3, 0x29, 0xed, 0x80, 0xbc, 0x4c, 0x7d, 0x40, 0xe8, 0xba, 0x9e, 0x99, 0xef, 0x6b, 0xea, 0xf9, 0x15, 0xd2, 0xdc, 0x7d, 0x9b, 0x54, 0x0a, 0x00, 0xac, 0x12, 0x09, 0x02, 0xbf, 0x03, 0xfc, 0x2b, 0x21, 0x8a, 0xef, 0x7c, 0x34, 0x17, 0x3b, 0xf4, 0xfc, 0x44, 0x3b, 0x68, 0x40, 0xd0, 0x8d, 0x4f, 0x60, 0xda, 0x41, 0x75, 0xf5, 0xf8, 0x23, 0x86, 0xab, 0x1b, 0x68, 0xfa, 0xdf, 0xfc, 0x10, 0x44, 0x7b, 0x07, 0xd8, 0x3c, 0x13, 0x78, 0x25, 0x82, 0xfd, 0xfd, 0x03, 0xad, 0x2f, 0xf7, 0x04, 0xb2, 0xb1, 0x64, 0x8e, 0x68, 0x51, 0x77, 0x1d, 0x27, 0x6b, 0x96, 0x23, 0xca, 0x46, 0xbf, 0x89, 0xa3, 0x5d, 0x83, 0xf8, 0x1c, 0xa1, 0xad, 0xcb, 0x7b, 0x14, 0xbf, 0xe0, 0x29, 0xe1, 0xae, 0xf5, 0xaf, 0xf8, 0x38, 0x84, 0x7a, 0x7a, 0x07, 0x90, 0x97, 0x50, 0xb4, 0x75, 0x3a, 0x2e, 0xd5, 0x03, 0xfb, 0x82, 0x3c, 0xc3, 0x1c, 0xe9, 0x50, 0x7f, 0x7c, 0xcd, 0xea, 0xf4, 0xbc, 0x08, 0x3e, 0x05, 0xcf, 0xcf, 0x55, 0xff, 0xe2, 0x6b, 0xc8, 0x3f, 0xf7, 0x27, 0x4b, 0x81, 0xd6, 0x2f, 0x6b, 0x40, 0xfe, 0x1e, 0x8b, 0xaa, 0xf6, 0x0f, 0x55, 0x17, 0xa7, 0xd0, 0x3f, 0xc5, 0xec, 0x2d, 0x03, 0xf8, 0x08, 0xd8, 0xbd, 0x0f, 0x94, 0xd7, 0x82, 0x68, 0xbb, 0x03, 0x7e, 0xcf, 0xdc, 0xb6, 0x7f, 0x02, 0x04, 0xb6, 0x0f, 0xe0, 0xf2, 0x1c, 0xb0, 0x5d, 0x6b, 0xff, 0x9b, 0x60, 0xbc, 0x18, 0xc1, 0x26, 0xcf, 0xef, 0x9a, 0x43, 0x6c, 0x03, 0xf8, 0x6f, 0xa5, 0x54, 0xff, 0xe0, 0x8f, 0xcd, 0x36, 0xe8, 0x1f, 0x03, 0x4f, 0x8e, 0x9b, 0x60, 0xf7, 0x1f, 0x35, 0x83, 0x9e, 0x9c, 0x18, 0x42, 0x57, 0xe8, 0x13, 0x05, 0xe3, 0xbf, 0x0d, 0xe9, 0x6b, 0xd3, 0xff, 0x2d, 0x1f, 0xf3, 0x6b, 0xf9, 0x6c, 0xd1, 0xf8, 0x3f, 0x9b, 0x40, 0xfc, 0x3d, 0x36, 0xd6, 0xa0, 0xc7, 0xc0, 0xaf, 0xa9, 0x22, 0xf6, 0x7e, 0xcf, 0xe6, 0xd0, 0x3f, 0x24, 0xb6, 0x0b, 0xe6, 0xaf, 0x72, 0x90, 0x5c, 0x77, 0xcf, 0xa5, 0x0b, 0x2e, 0x3d, 0xd5, 0x17, 0xaf, 0xc3, 0x59, 0xd2, 0x6b, 0xdf, 0xfc, 0x3d, 0xda, 0xcf, 0x74, 0x9e, 0x21, 0xc3, 0x34, 0x1f, 0xa7, 0x8e, 0x63, 0xb7, 0x81, 0x1e, 0x37, 0xbd, 0x29, 0xfa, 0xb9, 0x63, 0x7d, 0xda, 0x5a, 0x70, 0xef, 0xf8, 0x16, 0x43, 0x81, 0x58, 0x19, 0x88, 0x65, 0x2b, 0xde, 0x2b, 0x28, 0x91, 0x42, 0x5e, 0xe2, 0x3a, 0x5b, 0x7c, 0xbd, 0x3f, 0x07, 0x30, 0xfd, 0xf2, 0x60, 0xcb, 0x35, 0x14, 0xd6, 0x92, 0x51, 0x0e, 0x03, 0xcf, 0x24, 0x1d, 0xe6, 0xa4, 0x9d, 0x34, 0xe7, 0xfc, 0x39, 0x04, 0xbe, 0xd3, 0x5e, 0x2e, 0x16, 0x92, 0x81, 0xfc, 0x54, 0x27, 0xa3, 0xf4, 0xba, 0xf1, 0xbe, 0x02, 0x06, 0x4d, 0xed, 0xe0, 0xb2, 0x1a, 0xdf, 0x54, 0xff, 0xf8, 0x8f, 0x6d, 0x03, 0x40, 0x2f, 0xc9, 0xda, 0xe0, 0x6e, 0x87, 0xa6, 0x2f, 0xd0, 0x32, 0x17, 0xfb, 0xad, 0xaf, 0xf3, 0xc4, 0xdf, 0xb1, 0xfc, 0x04, 0x0c, 0x17, 0xcd, 0x24, 0xb7, 0xeb, 0x6b, 0xfc, 0x35, 0x11, 0x40, 0xf7, 0xa0, 0x6f, 0xc1, 0x0c, 0x5e, 0xff, 0x6b, 0x0f, 0xc5, 0x52, 0xeb, 0xf0, 0x67, 0x76, 0x0d, 0x7c, 0x3d, 0x0a, 0x6c, 0xb7, 0xed, 0xfa, 0x7c, 0x5c, 0x09, 0x37, 0xec, 0xf0, 0xa4, 0x47, 0xbb, 0x3f, 0x86, 0xe2, 0x2c, 0xfb, 0x06, 0xc1, 0xdf, 0x11, 0x1f, 0xb0, 0x17, 0xda, 0x7b, 0x5c, 0x0f, 0x90, 0x4f, 0x40, 0xfd, 0x2f, 0x5c, 0x0e, 0x12, 0x6c, 0x1f, 0x82, 0xf9, 0x2d, 0x6b, 0x87, 0xe1, 0x2b, 0x36, 0x13, 0x55, 0xf8, 0x88, 0xf9, 0x89, 0x72, 0x0c, 0xfd, 0x78, 0x66, 0x0b, 0x33, 0x8c, 0x1d, 0xd7, 0xe9, 0xeb, 0x86, 0x6e, 0x81, 0xd2, 0xe1, 0xc9, 0x36, 0x5e, 0x07, 0x98, 0x8d, 0x83, 0xfb, 0x58, 0x28, 0x84, 0x74, 0x5e, 0xfd, 0xaf, 0xbb, 0x07, 0xf7, 0xe4, 0xbd, 0x02, 0xf0, 0x2b, 0x42, 0xb4, 0x9f, 0xa2, 0xd7, 0xb7, 0xf8, 0x7e, 0xe8, 0x1f, 0xf7, 0xbf, 0xee, 0x60, 0x5f, 0xc5, 0xde, 0x81, 0xf8, 0x17, 0x64, 0xb0, 0x7f, 0x0e, 0x47, 0x52, 0xbd, 0x2f, 0xbf, 0x0c, 0xf8, 0x12, 0x6b, 0xdc, 0x1f, 0x78, 0x1f, 0x3c, 0x2d, 0xe0, 0x21, 0x62, 0x7a, 0xd7, 0xe0, 0x8a, 0x19, 0xf4, 0x6b, 0xd3, 0xfd, 0x7b, 0xfd, 0xf4, 0x0b, 0x8f, 0xf0, 0xcf, 0x8e, 0x82, 0x1f, 0x40, 0xe2, 0xa8, 0x10, 0xbc, 0x0a, 0x7f, 0xbf, 0x40, 0xf0, 0x43, 0xf8, 0x57, 0xd8, 0x2e, 0x86, 0xbd, 0x3f, 0x5c, 0x47, 0x60, 0xfa, 0x4a, 0xa0, 0x51, 0x9b, 0x7f, 0x02, 0x87, 0x8d, 0xfc, 0x47, 0x6d, 0x82, 0xe9, 0x6f, 0xc4, 0x49, 0xec, 0x1e, 0x26, 0x13, 0xe7, 0x1f, 0xf4, 0x3f, 0x5c, 0xb9, 0x72, 0x72, 0xfa, 0xf0, 0x42, 0x1e, 0xbf, 0x2e, 0x37, 0xc5, 0x70, 0xd2, 0xa4, 0x9c, 0xa8, 0x19, 0xb8, 0xd9, 0xe5, 0x53, 0x0d, 0x23, 0xd3, 0xc7, 0x01, 0x00, 0x3b, 0xb1, 0x23, 0x83, 0x79, 0x2f, 0x36, 0xdf, 0xde, 0x03, 0xa3, 0xd7, 0x51, 0x13, 0x3a, 0x12, 0x8f, 0xe3, 0xbf, 0x07, 0x41, 0xde, 0xe0, 0x8b, 0x0b, 0x82, 0xd9, 0xc0, 0x27, 0x18, 0x48, 0x3a, 0x1f, 0xf2, 0x77, 0x51, 0xd5, 0xcb, 0xe1, 0x7c, 0xbc, 0xa2, 0x4f, 0xb7, 0xf8, 0x32, 0x86, 0xfa, 0xd7, 0x5d, 0x1f, 0x8b, 0x18, 0xfd, 0xf7, 0xe3, 0x27, 0xaf, 0xed, 0xf3, 0x8c, 0x2f, 0xb3, 0x41, 0x9d, 0x83, 0xfd, 0xd7, 0xfd, 0xb0, }, { // nolint: dupl 0x80, 0x60, 0x75, 0x2a, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x01, 0xce, 0xff, 0xb3, 0xff, 0xb3, 0x82, 0x1f, 0x0f, 0x6a, 0xa5, 0xf4, 0xb0, 0x40, 0x15, 0xe0, 0x8a, 0x6f, 0x5c, 0xf1, 0x59, 0xb3, 0x4f, 0xc4, 0xe7, 0xfb, 0x0e, 0x06, 0xbd, 0xd7, 0xdb, 0xfe, 0x2f, 0x7e, 0xd7, 0x85, 0x20, 0x87, 0xad, 0xdd, 0xf8, 0x17, 0x60, 0x92, 0xfb, 0x1f, 0xce, 0x19, 0x84, 0x3d, 0x1d, 0x7e, 0xf8, 0x46, 0x27, 0xa5, 0xaf, 0x83, 0x48, 0x2d, 0xed, 0xdf, 0xbf, 0x13, 0x25, 0x2a, 0x7c, 0x09, 0xd2, 0x7a, 0xe5, 0x28, 0x6b, 0x7d, 0xcf, 0x7f, 0xc3, 0x52, 0xf5, 0xe1, 0x78, 0x5f, 0x74, 0xbd, 0x35, 0xa7, 0xfe, 0x15, 0xf4, 0x50, 0x41, 0xe9, 0x57, 0xab, 0x6b, 0x2b, 0xff, 0xf0, 0x49, 0xeb, 0x07, 0x28, 0x70, 0x31, 0xa4, 0x27, 0x8e, 0xa3, 0x3b, 0x33, 0x4a, 0xb6, 0xfa, 0x7c, 0x36, 0x10, 0x05, 0x11, 0x3a, 0x57, 0xa0, 0x51, 0xeb, 0x87, 0xa1, 0x8f, 0x60, 0x17, 0xb5, 0x73, 0x67, 0xf5, 0xc3, 0xd0, 0xde, 0xfa, 0xbf, 0xfe, 0x19, 0xe2, 0xae, 0xff, 0x12, 0x20, 0x57, 0xbb, 0xfc, 0x04, 0x2f, 0x81, 0x92, 0x5f, 0x7c, 0x11, 0x81, 0x00, 0x33, 0xdf, 0x15, 0x7f, 0xe0, 0xfe, 0x7a, 0xbf, 0xf5, 0x8c, 0x11, 0xec, 0x03, 0xbd, 0x41, 0x1d, 0x65, 0xc1, 0x0f, 0xa1, 0x87, 0xe5, 0xdb, 0xfe, 0xaa, 0x3b, 0x71, 0xc0, 0xb0, 0xf5, 0x6f, 0xfa, 0x8f, 0xf1, 0xe1, 0x0e, 0x3a, 0x7a, 0xd3, 0xff, 0x11, 0xf1, 0xb5, 0xd7, 0xbc, 0x23, 0x5d, 0x5f, 0xeb, 0xd7, 0x02, 0xf7, 0x89, 0xd5, 0x10, 0x39, 0xec, 0x6b, 0xf5, 0xf0, 0x90, 0x50, 0x11, 0x6d, 0x6d, 0xe5, 0x05, 0x73, 0x88, 0x3e, 0x64, 0x5b, 0x3a, 0x16, 0x7f, 0x20, 0x9d, 0x0f, 0xb1, 0x6a, 0xbe, 0x30, 0x2f, 0xa1, 0x71, 0x10, 0x80, 0x30, 0xbd, 0x00, 0x07, 0x78, 0x91, 0xfd, 0x73, 0x81, 0x80, 0x3b, 0xd5, 0x31, 0x5f, 0xca, 0x97, 0x88, 0xf1, 0xa9, 0x03, 0x7d, 0xbe, 0x74, 0xdf, 0x8d, 0x07, 0xbc, 0x08, 0xbe, 0x89, 0xc1, 0xd8, 0x12, 0x44, 0x6b, 0xef, 0xe0, 0x9c, 0x0c, 0x20, 0xba, 0xdd, 0xaa, 0x57, 0x95, 0x38, 0x21, 0xab, 0x70, 0x3e, 0x7a, 0x97, 0x6f, 0xe0, 0xf3, 0xe1, 0x2e, 0xeb, 0xbf, 0x88, 0x04, 0x9d, 0xbb, 0xfc, 0x39, 0xe9, 0x2f, 0xef, 0xd4, 0xbe, 0xde, 0xa0, 0x93, 0xdd, 0x8e, 0x4a, 0xbb, 0x9f, 0xc1, 0x08, 0x41, 0xd7, 0xe1, 0xb8, 0xaa, 0x7f, 0xbc, 0x3b, 0x9c, 0x40, 0xfb, 0x2f, 0xa7, 0xf6, 0x7f, 0x4f, 0x98, 0x3a, 0x13, 0xeb, 0xdb, 0xe1, 0xdf, 0x2c, 0x9e, 0xb8, 0x4f, 0xc5, 0x89, 0x3d, 0x7d, 0xbf, 0x81, 0xb2, 0xb5, 0xc0, 0xe9, 0x3f, 0xbd, 0x3f, 0xc1, 0x7c, 0x96, 0xfe, 0x09, 0x24, 0xb4, 0xbe, 0x1d, 0x0c, 0x04, 0xb4, 0x7d, 0xbf, 0xc2, 0xdd, 0xfd, 0xd6, 0x9f, 0xf8, 0x1b, 0x24, 0xf7, 0xc1, 0x34, 0xf5, 0xef, 0xf8, 0x58, 0x22, 0x15, 0xb5, 0xfa, 0x25, 0xbf, 0xf8, 0xc0, 0x68, 0xaf, 0xdf, 0x87, 0x21, 0xaf, 0x4d, 0x7a, 0x3f, 0xc3, 0x81, 0x91, 0xb5, 0xfa, 0xfc, 0x61, 0xb3, 0x95, 0xa0, 0x33, 0x4f, 0x6d, 0xba, 0x78, 0x39, 0x28, 0x20, 0x92, 0x1b, 0xea, 0x6c, 0x73, 0xe7, 0x77, 0xdb, 0xa7, 0xc3, 0xf5, 0x33, 0x7c, 0x97, 0xee, 0x22, 0x5b, 0xf5, 0x05, 0x3e, 0x37, 0xc0, 0x89, 0x04, 0xfd, 0xfd, 0x96, 0xb8, 0x3e, 0xad, 0x7c, 0xf5, 0xc9, 0xff, 0xeb, 0xae, 0x08, 0x8b, 0xdc, 0xf3, 0xa7, 0xbf, 0xb8, 0xf9, 0xeb, 0xa5, 0xff, 0xe3, 0xb5, 0x08, 0x06, 0x78, 0x5c, 0x64, 0x9c, 0x11, 0xf7, 0xe5, 0xd7, 0xc1, 0x07, 0x87, 0x17, 0xa4, 0x88, 0x08, 0x73, 0x17, 0x8b, 0x12, 0x08, 0xbd, 0x89, 0x2f, 0x90, 0x9a, 0x5e, 0x8a, 0x2b, 0x44, 0x97, 0x99, 0x4f, 0x04, 0xa1, 0x9f, 0x30, 0x65, 0xeb, 0x5f, 0xbc, 0x68, 0x5e, 0xe7, 0xfc, 0x4f, 0xf3, 0x25, 0x02, 0x95, 0xe2, 0x8e, 0xce, 0xbe, 0x09, 0x00, 0xa8, 0x18, 0xbd, 0xbc, 0xff, 0x8b, 0xe9, 0x87, 0xf5, 0xe2, 0x81, 0xe1, 0x7a, 0xf1, 0x00, 0xf3, 0x89, 0x2f, 0x0c, 0x81, 0x90, 0x2f, 0xed, 0xbc, 0x28, 0x0a, 0x92, 0xab, 0x12, 0x9b, 0xd3, 0xad, 0xbd, 0x01, 0x14, 0x3b, 0xa9, 0xfb, 0x32, 0xf9, 0xfe, 0x7b, 0xed, 0xf3, 0x77, 0xf8, 0x26, 0xaf, 0x71, 0x35, 0xd9, 0x5c, 0x12, 0x7b, 0xb3, 0xc4, 0x9e, 0xbe, 0xff, 0xcf, 0x5f, 0xed, 0xa8, 0x47, 0xc7, 0xc3, 0x9e, 0xaf, 0xfb, 0x7e, 0xa0, 0x9b, 0xd6, 0xbf, 0x75, 0x25, 0xbf, 0xaf, 0x0f, 0x42, 0x7a, 0xfd, 0xac, 0xbf, 0x82, 0x2e, 0xce, 0xe7, 0x17, 0x74, 0xbe, 0x07, 0xba, 0xdb, 0x0c, 0xe8, 0x3f, 0xff, 0xab, 0x7a, 0xe1, 0x0e, 0x37, 0x7c, 0xb8, 0xf8, 0x23, 0xbf, 0x7e, 0x09, 0x0a, 0x2b, 0x5f, 0x5f, 0x18, 0x08, 0xed, 0x77, 0xe1, 0xc0, 0xa8, 0x27, 0xdb, 0xf7, 0xd7, 0x15, 0x0e, 0x69, 0x6b, 0x46, 0xaf, 0xf8, 0x2d, 0xf0, 0xf9, 0x57, 0xb9, 0x08, 0x1c, 0xe8, 0xab, 0xbf, 0xf0, 0x45, 0xe0, 0xbe, 0x7a, 0xdf, 0xfc, 0x37, 0x04, 0x5e, 0xd3, 0x3c, 0x3a, 0x1a, 0x94, 0x97, 0x78, 0xa9, 0xf6, 0xd6, 0x9e, 0xc3, 0x22, 0x2a, 0xb8, 0xdc, 0x82, 0xaa, 0x6b, 0xc3, 0x50, 0x4d, 0x5e, 0xfc, 0xc9, 0xc3, 0x3a, 0xa2, 0x73, 0x90, 0xbe, 0x9e, 0xc1, 0x08, 0x24, 0xef, 0xd7, 0x06, 0x9e, 0x88, 0xbd, 0xc0, 0x82, 0x53, 0xe2, 0xe9, 0x7f, 0x8c, 0x04, 0x7b, 0xf2, 0xfa, 0xd5, 0x41, 0x0f, 0x82, 0x1a, 0xd6, 0x27, 0xe8, 0x31, 0xc7, 0x4f, 0x63, 0x5f, 0xfd, 0x62, 0xe1, 0x5f, 0x0e, 0xf8, 0xe0, 0xcc, 0xd8, 0x39, 0xf0, 0xe9, 0x7a, 0x7f, 0x5a, 0xae, 0xb5, 0xf5, 0xee, 0x24, 0x9c, 0x1d, 0x06, 0x0d, 0xd7, 0x8a, 0x02, 0x80, 0x77, 0x7d, 0xf8, 0x9e, 0x28, 0xa6, 0x2e, 0x23, 0x22, 0xab, 0x7d, 0x3f, 0x30, 0x20, 0x57, 0xe5, 0x21, 0xb7, 0xf9, 0x6f, 0x18, 0x09, 0xec, 0x0b, 0xd3, 0xf2, 0xe7, 0x28, 0x7a, 0x99, 0x6e, 0xd0, 0x55, 0x53, 0xff, 0x1e, 0xc7, 0x65, 0x38, 0xc2, 0x5a, 0x5e, 0x2f, 0xe0, 0x46, 0x04, 0xa1, 0xff, 0x42, 0x3d, 0x91, 0x9c, 0x1e, 0x02, 0x9f, 0xc5, 0x74, 0x1f, 0x1e, 0xa0, 0xe9, 0x62, 0x36, 0x3b, 0xb6, 0xdc, 0x46, 0x89, 0xe1, 0x30, 0xe9, 0x78, 0xe2, 0xf8, 0x48, 0x80, 0x93, 0x0a, 0x2b, 0xbe, 0xe0, 0xd2, 0x36, 0xa2, 0x0e, 0x07, 0x3c, 0x85, 0xe6, 0x43, 0xe7, 0x83, 0x3d, 0xa0, 0xff, 0xa0, 0x37, 0xc3, 0x56, 0x30, 0x03, 0x09, 0xed, 0x34, 0xd2, 0xb6, 0xed, 0x70, 0x7c, 0x08, 0x05, 0xf4, 0x71, 0x9f, 0xcd, 0x78, 0x29, 0x82, 0xeb, 0xa5, 0xdf, 0x83, 0x88, 0x1a, 0xf5, 0xf1, 0x03, 0x4f, 0x5f, 0xb7, 0xec, 0x40, 0x73, 0x6b, 0x5f, 0xd7, 0xb8, 0xbe, 0xda, 0xe7, 0x27, 0xcd, 0x08, 0xfb, 0x58, 0x33, 0x17, 0x5e, 0x32, 0x18, 0x7f, 0x05, 0x1d, 0x03, 0xe9, 0x58, 0xe3, 0x80, 0x8a, 0x09, 0x3a, 0x77, 0xe0, 0x4c, 0x8f, 0xed, 0xf7, 0xd2, 0xf1, 0x33, 0xd7, 0xfb, 0x79, 0x83, 0xbc, 0x33, 0xe1, 0xb1, 0x80, 0x86, 0xdd, 0x57, 0xb9, 0x20, 0xc3, 0xae, 0xba, 0xfd, 0xbf, 0x08, 0xc3, 0x1d, 0x7c, 0xd9, 0x5f, 0xe9, 0xe2, 0xa0, 0xaf, 0x4f, 0xc7, 0x99, 0xd4, 0x94, 0x6c, 0x97, 0xcf, 0xc3, 0x84, 0x0e, 0xf3, 0x67, 0x6f, 0x87, 0x13, 0x4b, 0xee, 0xdf, 0xf0, 0x22, 0x55, 0xf8, 0x44, 0x09, 0x21, 0x9f, 0x57, 0xfe, 0xfc, 0x0e, 0x03, 0x47, 0xd1, 0x7e, 0xed, 0xfc, 0x0b, 0x41, 0x93, 0x57, 0xe0, 0x94, 0x81, 0x7a, 0xf5, 0xf0, 0x7b, 0xfe, 0x26, 0x18, 0xd2, 0x77, 0xe6, 0xc5, 0xfe, 0xde, 0x1d, 0x82, 0xcb, 0x5f, 0x68, 0x94, 0xd5, 0xaa, 0x55, 0x82, 0x61, 0x27, 0x9c, 0xa9, 0xdb, 0xfe, 0x0b, 0x08, 0x08, 0xfd, 0x24, 0xab, 0x08, 0xc3, 0x96, 0xba, 0xeb, 0xfc, 0xa0, 0xb5, 0xfb, 0x78, 0x78, 0x10, 0x1f, 0x06, 0x8b, 0xfc, 0x12, 0x94, 0xf5, 0x6b, 0xfe, 0x1a, 0x21, 0x34, 0xfe, 0x18, 0x13, 0xdf, 0x7f, 0x90, 0x39, 0xed, 0x65, 0xdb, 0xfe, 0x0d, 0xa0, 0x93, 0x5b, 0x46, 0xfc, 0x79, 0x4f, 0x7f, 0x4f, 0xf8, 0xc7, 0x6b, 0xf1, 0x07, 0xf5, 0x3f, 0xf9, 0x8a, 0x22, 0xdf, 0xbf, 0x82, 0x1b, 0xef, 0xc0, 0x96, 0x14, 0x2e, 0xfe, 0x30, 0x4f, 0x07, 0xc4, 0xe0, 0xc3, 0xf2, 0x7a, 0x5c, 0x12, 0x70, 0x41, 0xf1, 0xbe, 0x40, 0x43, 0xaf, 0xde, 0x5e, 0x0c, 0x3c, 0x14, 0x78, 0x50, 0xbe, 0x23, 0x81, 0x26, 0x7a, 0xda, 0xff, 0x1f, 0x3d, 0x77, 0x5f, 0x24, 0xfa, 0xc1, 0x6f, 0xd6, 0xaf, 0xae, 0x5c, 0x41, 0x78, 0x12, 0xa3, 0x75, 0xee, 0x44, 0x9e, 0x21, 0xcd, 0x6c, 0xca, 0x9d, 0x3f, 0x6f, 0x02, 0x40, 0x3d, 0x0d, 0xf0, 0x79, 0xe0, 0x68, 0x65, 0x88, 0x73, 0x85, 0x7f, 0xf1, 0x0e, 0xf1, 0x64, 0x04, 0x99, 0xa9, 0xd9, 0xc8, 0x40, 0x43, 0xa9, 0xa1, 0xdf, 0xcb, 0x78, 0x64, 0x39, 0xb7, 0xcb, 0x45, 0xff, 0x0e, 0x9e, 0xc6, 0x6f, 0xfe, 0x61, 0xa1, 0xe9, 0xf2, 0xa7, 0xae, 0xc2, 0x72, 0xad, 0x9f, 0xf6, 0xcd, 0x23, 0x4e, 0x5e, 0x48, 0x2d, 0x63, 0x23, 0x5b, 0xe9, 0x8b, 0xf8, 0x22, 0x04, 0x22, 0x7d, 0xcb, 0x53, 0x30, 0xd7, 0x75, 0xff, 0x0c, 0xc1, 0x46, 0xce, 0x71, 0xfd, 0x52, 0x7e, 0x17, 0x28, 0xbe, 0xe8, 0x16, 0x71, 0x87, 0xa8, 0x25, 0xec, 0xf0, 0xba, 0xfa, 0xab, 0x5b, 0xf7, 0x2c, 0xc2, 0x2f, 0xe2, 0x46, 0x1e, 0xcf, 0xd7, 0xe5, 0x56, 0x39, 0x0a, 0x14, 0xf5, 0xb2, 0x4c, 0x78, 0x34, 0x0c, 0xa2, 0x43, 0x97, 0x36, 0x93, 0xf6, 0x1b, 0x84, 0xe5, 0x2f, 0xfd, 0x84, 0x8f, 0xc5, 0xf3, 0x4c, 0x74, 0x89, 0xe5, 0x39, 0xe1, 0x2b, 0x06, 0xce, 0x3e, 0x3b, 0x74, 0x0b, 0x8f, 0x08, 0x82, 0x6f, 0x36, 0x67, 0x16, 0xd8, 0x42, 0xbb, 0xfb, 0xff, 0xfc, 0x0d, 0x50, 0x45, 0xd2, 0xb7, 0x5e, 0x18, 0x02, 0x09, 0xb7, 0xf0, 0x5f, 0x3d, 0x7d, 0x3f, 0x84, 0x08, 0x08, 0x3c, 0xb3, 0xb7, 0x3f, 0x2f, 0xf6, 0xbb, 0x4f, 0xfc, 0xc4, 0x05, 0x58, 0xcf, 0x87, 0xf4, 0xd8, 0x21, 0x61, 0xbf, 0xa3, 0x63, 0xa2, 0x84, 0x71, 0x7a, 0x55, 0xd0, 0x09, 0x78, 0xc0, 0x94, 0x74, 0x78, 0x2a, 0x4b, 0xfe, 0x03, 0xc6, 0x5b, 0xe9, 0xe1, 0x58, 0xfd, 0xd8, 0x34, 0x5b, 0x53, 0x2b, 0x76, 0xf8, 0x40, 0x67, 0x03, 0x88, 0xc3, 0xdf, 0x6e, 0x8f, 0xf3, 0xc3, 0xdd, 0xad, 0xa9, 0x31, 0x17, 0xe5, 0xfe, 0x9e, 0x68, 0x77, 0x5b, 0x74, 0x88, 0x19, 0xd0, 0x5d, 0x6d, 0x73, 0xff, }, { // nolint: dupl 0x80, 0x60, 0x75, 0x2b, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x01, 0x1e, 0x40, 0x57, 0x1c, 0x2e, 0xef, 0xf4, 0x7b, 0x07, 0x32, 0x73, 0x44, 0xec, 0xfe, 0x80, 0xf0, 0x52, 0x40, 0x55, 0x7d, 0x05, 0x8f, 0xcd, 0xf9, 0xb2, 0xad, 0xc4, 0x94, 0x2d, 0x4b, 0xa3, 0xad, 0x7a, 0x7f, 0x85, 0x8a, 0x7a, 0xd2, 0xa7, 0xf8, 0x2e, 0x10, 0xfa, 0x5c, 0x0a, 0x37, 0xd2, 0xe0, 0x90, 0x1e, 0x06, 0xba, 0x3a, 0xdb, 0xff, 0x89, 0x3d, 0x74, 0xff, 0xc4, 0x0d, 0xdf, 0xec, 0x16, 0xe6, 0x03, 0x3a, 0x06, 0xbc, 0x71, 0x6a, 0x9f, 0xdb, 0xe6, 0x28, 0xdd, 0x58, 0x32, 0x5b, 0x0f, 0xa3, 0xbf, 0xd0, 0x37, 0x3f, 0x6f, 0xce, 0x40, 0xfd, 0x82, 0xfa, 0xa0, 0x59, 0x73, 0x7f, 0xdb, 0xfc, 0x15, 0x54, 0x39, 0x0c, 0xeb, 0xf3, 0x0b, 0x0f, 0x0c, 0xfa, 0xb6, 0x3c, 0x22, 0x0a, 0xfc, 0x0e, 0x41, 0xec, 0x82, 0x0e, 0xd6, 0x61, 0xc2, 0x3c, 0x14, 0x90, 0xf5, 0xd7, 0xf8, 0x22, 0x9b, 0xc7, 0x4e, 0x3a, 0x28, 0x29, 0xbf, 0xd9, 0xa0, 0x74, 0x0e, 0x87, 0x6e, 0x15, 0x28, 0x52, 0x53, 0x80, 0x5d, 0xaa, 0x7f, 0xb0, 0x82, 0x34, 0xd5, 0x25, 0x11, 0x7b, 0x3e, 0x1d, 0x9f, 0x51, 0x9e, 0x3e, 0xa4, 0x47, 0x78, 0x2f, 0x8c, 0xc0, 0x63, 0x72, 0xbd, 0xe6, 0xbe, 0xc8, 0x14, 0x96, 0x97, 0x70, 0x9f, 0x40, 0xe8, 0x16, 0x13, 0x30, 0x7b, 0xf7, 0x04, 0x35, 0x83, 0x82, 0x19, 0xb7, 0xfc, 0x11, 0x5f, 0xf7, 0xcf, 0x71, 0xbf, 0xf0, 0x53, 0x37, 0x4b, 0xe1, 0x4f, 0x40, 0x90, 0x34, 0x0e, 0xa4, 0x22, 0xc1, 0xff, 0xa7, 0xdc, 0x5c, 0x76, 0xc1, 0xe6, 0x1a, 0x40, 0xf7, 0xb5, 0xc5, 0x10, 0x3d, 0x98, 0xd4, 0x86, 0x76, 0x07, 0x48, 0xce, 0xd8, 0xfa, 0x7f, 0x1d, 0x0d, 0xc3, 0xed, 0x37, 0x7c, 0xb6, 0xbf, 0x59, 0x7a, 0xf4, 0x51, 0xbe, 0x68, 0x6a, 0x91, 0x05, 0x76, 0x3d, 0xf5, 0xfe, 0xde, 0x04, 0x68, 0x52, 0x32, 0xd1, 0x54, 0x53, 0x50, 0xd4, 0xed, 0x25, 0xe8, 0x1b, 0x0f, 0x1b, 0x54, 0x02, 0xa4, 0x36, 0xbe, 0xcf, 0x6a, 0x9f, 0x5f, 0x4f, 0xf8, 0x62, 0x8d, 0xe9, 0x64, 0xba, 0xfb, 0x7f, 0x89, 0xfa, 0xc1, 0x5c, 0x21, 0xbf, 0x66, 0xc9, 0x8c, 0x84, 0x1d, 0x7c, 0x4c, 0x14, 0xda, 0xf6, 0x13, 0x5d, 0xbd, 0xf8, 0x11, 0x61, 0xdd, 0x07, 0xf6, 0xf7, 0x6a, 0xbf, 0x93, 0x39, 0x48, 0x0c, 0x3d, 0x07, 0x41, 0x61, 0x13, 0x17, 0x78, 0x36, 0x74, 0xff, 0xc4, 0x08, 0x0d, 0xf7, 0xba, 0x28, 0xa4, 0xdf, 0xf8, 0x4c, 0xbf, 0x13, 0xe6, 0x66, 0x70, 0xcd, 0x89, 0xe1, 0x41, 0x5a, 0xe5, 0x0c, 0x0f, 0xf3, 0xd5, 0x97, 0xfc, 0x48, 0x40, 0x17, 0xf4, 0x27, 0xd8, 0xe2, 0x6c, 0x31, 0xfa, 0xaf, 0x45, 0x15, 0xe3, 0x22, 0x1c, 0x3c, 0x92, 0xfe, 0xbf, 0xe0, 0x86, 0xc5, 0x02, 0xec, 0xe1, 0x60, 0xc0, 0x22, 0xd8, 0x3a, 0x95, 0x3b, 0x20, 0x5a, 0x60, 0x41, 0xc1, 0xe3, 0x40, 0xb3, 0xa4, 0xe3, 0xf4, 0xfe, 0x1e, 0xd0, 0xfd, 0x29, 0x18, 0xb1, 0x58, 0xdf, 0xfc, 0x45, 0xf7, 0x7c, 0xc3, 0x46, 0xe9, 0x76, 0xd5, 0xb7, 0xbc, 0x78, 0xd7, 0xfa, 0xdf, 0xfc, 0xa2, 0x01, 0x86, 0x07, 0xec, 0x0e, 0x75, 0xd7, 0xfb, 0xf8, 0x6f, 0x6e, 0xda, 0xfe, 0xdf, 0x84, 0x16, 0xbb, 0x28, 0x2b, 0x9e, 0x16, 0xc7, 0xe0, 0x49, 0xfe, 0x39, 0x5e, 0xce, 0x88, 0x17, 0x98, 0x30, 0x14, 0xe5, 0x8f, 0x41, 0x2f, 0xd3, 0xfc, 0xf8, 0xbd, 0x3f, 0xc6, 0x4d, 0xa5, 0xe3, 0x46, 0x92, 0xbf, 0x1e, 0x30, 0x13, 0xd7, 0xeb, 0x7f, 0x07, 0x80, 0xc3, 0xcb, 0xfa, 0x09, 0x7f, 0xa7, 0x85, 0xc1, 0x60, 0x5a, 0x58, 0x13, 0xab, 0xc2, 0x77, 0x0f, 0xf0, 0xfb, 0xaf, 0xa7, 0xf7, 0x1d, 0xe4, 0xeb, 0xae, 0x85, 0x10, 0xed, 0x7e, 0xe0, 0x9a, 0xa9, 0xf7, 0xab, 0x9d, 0xc1, 0x3c, 0x80, 0x00, 0x19, 0x89, 0xdb, 0xcc, 0x28, 0x8b, 0x4e, 0x42, 0x6f, 0xe4, 0x18, 0x10, 0xe9, 0x73, 0x76, 0x3e, 0xf0, 0x8e, 0x80, 0x84, 0x6a, 0xff, 0xf1, 0xc1, 0x90, 0xb6, 0xef, 0xb0, 0x27, 0x38, 0xbd, 0x9d, 0xfb, 0x2e, 0xc4, 0x02, 0x7e, 0xa3, 0x86, 0x41, 0xfb, 0x8d, 0xdf, 0x3d, 0xf9, 0x61, 0x98, 0xe3, 0x5f, 0xc5, 0xa7, 0xfe, 0x52, 0x8e, 0xb5, 0x9f, 0xd1, 0xcb, 0xfa, 0xe4, 0x20, 0x9e, 0x94, 0x3a, 0xa6, 0x78, 0xfc, 0xe6, 0x26, 0x0a, 0xf3, 0xaa, 0xbd, 0x8b, 0x2e, 0x69, 0x4b, 0x85, 0x49, 0x87, 0x75, 0xff, 0xdf, 0xfd, 0xbf, 0xf8, 0xb1, 0x25, 0xf7, 0xc1, 0x28, 0x80, 0xd7, 0xb6, 0xb7, 0xff, 0x44, 0x08, 0x69, 0x72, 0xfe, 0x84, 0xe7, 0x51, 0x89, 0x20, 0x4f, 0x40, 0x45, 0xff, 0x45, 0xca, 0x14, 0xda, 0x8c, 0x16, 0x61, 0xe4, 0x34, 0x7f, 0x4e, 0x87, 0xdd, 0xaf, 0x97, 0x0c, 0xc3, 0xda, 0xfa, 0xf5, 0xd7, 0xd3, 0xfc, 0xa0, 0xaa, 0x1c, 0x5a, 0xaa, 0x0b, 0x8f, 0x42, 0xe4, 0x13, 0x87, 0x50, 0x62, 0x1e, 0xfc, 0x74, 0x50, 0xb7, 0x30, 0x40, 0x3d, 0xee, 0x1a, 0xba, 0x27, 0x12, 0xc6, 0x2f, 0xe0, 0xf5, 0xfe, 0xbc, 0x85, 0xe1, 0xb6, 0xd9, 0xf9, 0x56, 0xfe, 0x40, 0x5f, 0x47, 0xf2, 0x69, 0x97, 0xa3, 0xc9, 0x5d, 0xbf, 0xf6, 0x41, 0x9e, 0xfb, 0x31, 0xa0, 0x64, 0xe8, 0x31, 0xce, 0xfe, 0x08, 0xc1, 0x00, 0x56, 0x1b, 0x9f, 0x16, 0x5f, 0xdf, 0x16, 0x66, 0x26, 0x47, 0x45, 0xe1, 0x4c, 0xbe, 0xd8, 0x97, 0x7d, 0x10, 0x12, 0xf4, 0x93, 0x87, 0xb2, 0xa9, 0x72, 0x2f, 0xb8, 0x4b, 0xdf, 0x4e, 0x66, 0xec, 0x48, 0x25, 0x8c, 0x65, 0xe7, 0x0c, 0x3b, 0xdf, 0x08, 0xee, 0x55, 0x1d, 0x7f, 0x82, 0x0b, 0xbf, 0x5d, 0x7b, 0xca, 0x1c, 0xf4, 0x1a, 0xb2, 0xff, 0xb2, 0x0f, 0xba, 0x2a, 0x4d, 0x7e, 0x55, 0x5e, 0x0a, 0x79, 0x81, 0x67, 0x36, 0x42, 0x6f, 0x27, 0xda, 0xfb, 0x0d, 0x10, 0x11, 0x49, 0x8c, 0x1f, 0x17, 0x94, 0x3d, 0xbe, 0xfc, 0xa1, 0x00, 0xa1, 0xeb, 0xf3, 0xe7, 0xca, 0x37, 0x29, 0x05, 0x76, 0x07, 0x21, 0x8e, 0xbe, 0x22, 0xcd, 0xd7, 0xd7, 0xec, 0x87, 0xf7, 0xaf, 0xf2, 0x76, 0x50, 0x97, 0x1d, 0x0b, 0x9d, 0x58, 0xfb, 0x87, 0x69, 0x5f, 0xa3, 0xcc, 0x11, 0x44, 0x72, 0x20, 0x57, 0xd3, 0xd7, 0x90, 0x94, 0x88, 0x6b, 0xd9, 0x42, 0xba, 0x11, 0x0d, 0xe6, 0x05, 0xfe, 0xfd, 0x7f, 0x05, 0x57, 0x6b, 0xd0, 0x3b, 0x55, 0xe4, 0xfd, 0x08, 0x13, 0xc7, 0x13, 0x3d, 0x0a, 0xed, 0xf2, 0xfc, 0x9e, 0x74, 0x3e, 0x37, 0x43, 0xfa, 0x31, 0xad, 0xff, 0x32, 0x9a, 0xfd, 0x3f, 0x31, 0x5e, 0xc2, 0xf1, 0x30, 0x4b, 0x40, 0xd8, 0x2e, 0x81, 0xfe, 0xe2, 0x61, 0x4e, 0x80, 0xf8, 0xba, 0x0b, 0x54, 0xed, 0xb8, 0xe2, 0x41, 0x5e, 0x7b, 0xfd, 0x8e, 0xee, 0x89, 0xe1, 0x9b, 0xc6, 0x13, 0xb0, 0x2f, 0x02, 0x48, 0x77, 0x5f, 0xaf, 0x7c, 0xcf, 0x7f, 0xd1, 0x42, 0xd4, 0xff, 0x30, 0x46, 0xac, 0xbf, 0xf8, 0x9f, 0x1a, 0xbb, 0xdb, 0xf9, 0x2d, 0x7e, 0x08, 0x5f, 0x5f, 0x8f, 0xed, 0x74, 0x05, 0xb1, 0xd8, 0xf4, 0x40, 0x45, 0xac, 0x3f, 0x16, 0x65, 0xf8, 0x92, 0x86, 0x29, 0x3e, 0xfd, 0xfe, 0xbf, 0x28, 0x40, 0x76, 0x76, 0x23, 0x82, 0x43, 0xd8, 0xdf, 0xf2, 0x09, 0x08, 0xd0, 0x16, 0xde, 0x50, 0xdd, 0x80, 0xe2, 0xfc, 0xa5, 0xde, 0xf8, 0xf2, 0x16, 0xed, 0xfa, 0x28, 0x27, 0xe3, 0xe3, 0xbf, 0xca, 0x91, 0xdf, 0x9a, 0xec, 0xa5, 0xa7, 0x87, 0xd0, 0x80, 0xb5, 0xec, 0x0f, 0xb2, 0xef, 0x6f, 0xff, 0x28, 0x24, 0xf1, 0xe2, 0x43, 0x8c, 0x51, 0x01, 0x46, 0x81, 0x28, 0x68, 0xd8, 0x2a, 0x61, 0x89, 0xbc, 0xc9, 0x78, 0xe0, 0x88, 0x66, 0x9f, 0x5d, 0xbb, 0x2a, 0x5c, 0x2c, 0x0c, 0x41, 0x64, 0xd1, 0xd8, 0x13, 0x1f, 0x4d, 0x87, 0x42, 0xd7, 0x30, 0x44, 0x13, 0xed, 0x73, 0x86, 0x0c, 0xe8, 0x5f, 0xe1, 0x9b, 0xba, 0x7a, 0xad, 0x6f, 0xf1, 0x03, 0x3c, 0x3a, 0xbe, 0x3c, 0xa2, 0x79, 0x04, 0x0d, 0xa6, 0x2a, 0x16, 0x51, 0x20, 0xab, 0x43, 0x91, 0x94, 0xf4, 0x9a, 0x33, 0xf3, 0x20, 0xfd, 0xe2, 0x0f, 0x57, 0xff, 0xe2, 0x79, 0x03, 0x32, 0x1e, 0xfa, 0x12, 0x0a, 0x76, 0x41, 0x95, 0x27, 0x40, 0x9f, 0xcb, 0x6d, 0xdf, 0x08, 0xe6, 0x10, 0xed, 0xd3, 0xb7, 0xf2, 0x0d, 0x04, 0x9a, 0x5f, 0x73, 0x85, 0x01, 0x3c, 0x9e, 0x36, 0x87, 0xd0, 0xf1, 0x8a, 0x52, 0x3c, 0x60, 0x38, 0x05, 0x57, 0xef, 0xe1, 0xf6, 0xdf, 0xa3, 0x48, 0xf1, 0x40, 0xb3, 0xca, 0x17, 0xbb, 0x7e, 0xc1, 0x8f, 0x42, 0xeb, 0x4f, 0xfc, 0x64, 0x3f, 0xbe, 0xa9, 0xe4, 0x24, 0xd5, 0xe6, 0x34, 0xab, 0xf4, 0xfb, 0x08, 0x02, 0x2f, 0x49, 0xfa, 0x12, 0x1a, 0x94, 0x2f, 0xa0, 0x2a, 0xb7, 0xeb, 0xe8, 0xa0, 0x92, 0xc3, 0xa1, 0xd8, 0x68, 0x43, 0xf4, 0xfc, 0xf5, 0xb6, 0xbf, 0xc4, 0x84, 0x44, 0x7b, 0xe0, 0xfe, 0x34, 0x2d, 0xd8, 0x38, 0x49, 0xf6, 0xfc, 0x01, 0xd7, 0x7e, 0xbd, 0x8d, 0x05, 0x75, 0x42, 0x8f, 0x7a, 0x97, 0x98, 0xe4, 0x81, 0x9d, 0xe4, 0x4f, 0x12, 0x1c, 0xde, 0x96, 0xff, 0xeb, 0xc8, 0xe3, 0x82, 0xf3, 0xa5, 0xd0, 0x91, 0xd8, 0xf8, 0x02, 0x3d, 0x7f, 0x7f, 0x07, 0xa7, 0xf8, 0xdf, 0xff, 0x35, 0xfe, 0x34, 0x32, 0x1e, 0xc3, 0xfa, 0xcf, 0x07, 0x28, 0x86, 0xd7, 0x34, 0xbe, 0xcb, 0xa2, 0xc8, 0x20, 0x16, 0x69, 0x7b, 0xd0, 0xf2, 0xea, 0x70, 0xe0, 0xf7, 0x82, 0x1a, 0xf7, 0x04, 0x23, 0x7c, 0x87, 0xad, 0x3f, 0xf6, 0x40, 0x4f, 0x1a, 0x3f, 0xcb, 0x72, 0xb1, 0x6f, 0xb3, 0xca, 0x11, 0x95, 0x9a, 0xc7, 0x41, 0xcb, 0xa0, 0x27, 0x74, 0xd2, 0x7a, 0xe8, 0xfa, 0xae, 0x18, 0xdd, 0xc5, 0xc3, 0xa8, 0x6b, 0x7d, 0x7e, 0xcb, 0xf0, 0xb5, 0x28, 0xd0, 0xc5, 0xd7, 0x41, 0x7a, 0x69, 0xff, 0xb6, 0xfb, 0x12, 0x10, 0x92, 0x51, 0xa1, 0xdf, 0xd0, 0x5f, 0xbf, 0x21, 0xb4, 0x07, 0xf2, 0x04, 0xf7, 0xe6, 0x0d, 0xbd, 0x2c, 0xc5, 0x04, 0x31, 0xc3, 0x15, 0xc2, 0xd7, 0x1c, 0x4f, 0x27, 0x70, 0x96, 0xff, 0x62, 0xe4, 0x1b, 0xa0, 0x3f, 0x7e, 0xf2, 0x98, 0xc3, 0xd6, 0xd7, 0x63, 0xfd, 0x3e, 0x08, 0x7c, 0x81, 0x6d, 0x8b, 0xb5, 0x57, 0x1f, 0x7f, 0x90, 0x14, 0xf9, 0x57, 0x60, 0x5e, 0xfe, 0xfd, 0xfb, 0x9e, 0xa9, 0xff, 0xe4, 0xab, 0x2f, 0x8c, 0x13, 0xe8, 0x0a, 0xc3, 0xde, 0x15, 0x35, 0x3f, 0x94, 0x28, 0x32, 0xdf, 0x7b, 0x99, 0xba, }, { // nolint: dupl 0x80, 0x60, 0x75, 0x2c, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x01, 0x5b, 0x1f, 0xc9, 0xe5, 0x04, 0xd2, 0x04, 0x03, 0x45, 0x24, 0xed, 0x72, 0xae, 0x08, 0x7b, 0x57, 0xee, 0x1e, 0xe9, 0x69, 0xbe, 0x80, 0xa6, 0x0c, 0x05, 0x5b, 0xfd, 0x7c, 0x99, 0x83, 0x7e, 0x8a, 0x08, 0xe7, 0x5f, 0xce, 0x73, 0x03, 0x51, 0x37, 0x1a, 0x24, 0x76, 0x05, 0xf6, 0x41, 0xb8, 0xf0, 0x91, 0x7e, 0x3f, 0x61, 0x5b, 0x6b, 0xaf, 0xf5, 0xd8, 0x20, 0xe8, 0x1c, 0x55, 0x0c, 0x13, 0x1f, 0xa7, 0x05, 0x40, 0x3f, 0xb2, 0x85, 0xa9, 0xb0, 0xd0, 0x44, 0x66, 0x80, 0xa7, 0x5d, 0x7d, 0xfe, 0x88, 0x08, 0x21, 0xa4, 0x4c, 0x8f, 0x60, 0x67, 0x45, 0x9a, 0x38, 0x74, 0x2e, 0xa4, 0x16, 0xbf, 0xb7, 0xc8, 0x51, 0x5b, 0x8d, 0xf9, 0x70, 0xc2, 0x0e, 0x88, 0x14, 0x91, 0x7a, 0x6d, 0x35, 0xd8, 0xdf, 0xf5, 0xc7, 0xdd, 0xfb, 0xe8, 0x48, 0x5e, 0x8b, 0x60, 0x54, 0x05, 0x40, 0xaf, 0xeb, 0xf9, 0x7d, 0x89, 0xee, 0x5d, 0x7e, 0x20, 0x66, 0x1f, 0x77, 0xf8, 0x3e, 0xc7, 0x94, 0x47, 0x74, 0x68, 0x3e, 0xc1, 0x08, 0x7b, 0x43, 0xd2, 0x6b, 0x38, 0xa7, 0x84, 0xc1, 0x01, 0x64, 0xbf, 0x4f, 0xe3, 0x47, 0xf6, 0x9e, 0x04, 0xd9, 0xc3, 0xfc, 0xf1, 0xe0, 0xa3, 0x14, 0xfe, 0x6c, 0xab, 0xce, 0x19, 0xe2, 0x07, 0xf4, 0xfb, 0x4f, 0xcb, 0xb8, 0x44, 0x8e, 0xd2, 0xf8, 0x40, 0xa1, 0x9e, 0xf6, 0xdf, 0xfc, 0x68, 0x44, 0x13, 0xf3, 0x30, 0x5e, 0x1d, 0x93, 0xcd, 0xd8, 0x4b, 0x8d, 0x05, 0xbf, 0x13, 0x7b, 0x4e, 0x9d, 0x3f, 0x20, 0xec, 0xa9, 0xc6, 0x44, 0x05, 0xe9, 0x3e, 0xff, 0xca, 0x2a, 0x81, 0x89, 0x65, 0x30, 0x9e, 0xc0, 0x99, 0x6a, 0x11, 0xda, 0xf7, 0xdf, 0xd8, 0xd1, 0x34, 0xd8, 0xa7, 0x74, 0x64, 0xdb, 0xf6, 0x24, 0x4f, 0x8d, 0x83, 0x9d, 0x04, 0x5d, 0x0a, 0x91, 0x79, 0x85, 0xa1, 0xcd, 0xfc, 0xb7, 0x4f, 0xf8, 0x29, 0x82, 0x3d, 0x27, 0xf6, 0x19, 0xce, 0x67, 0xf4, 0x2f, 0xfd, 0x7c, 0x4d, 0x44, 0xab, 0xd8, 0x14, 0x81, 0x99, 0x83, 0xfc, 0x20, 0x3b, 0x40, 0x73, 0x1d, 0xb6, 0xf9, 0x82, 0xd3, 0x2f, 0xc4, 0x87, 0x3c, 0x8c, 0xcb, 0xfd, 0x78, 0xc1, 0x5e, 0x48, 0x41, 0xac, 0x37, 0xde, 0x17, 0xc6, 0x08, 0x0c, 0xc7, 0xce, 0xfe, 0x04, 0x53, 0xaf, 0xf1, 0x22, 0x03, 0x3a, 0xfa, 0xb7, 0xff, 0x11, 0x26, 0xfe, 0x86, 0x0a, 0xd1, 0x8e, 0xa6, 0x23, 0xc3, 0x8a, 0x1a, 0x59, 0x4a, 0x11, 0xc7, 0x81, 0x09, 0x60, 0x60, 0xf7, 0xce, 0x9d, 0x8b, 0x31, 0x78, 0x20, 0x0c, 0x2c, 0x1f, 0x5d, 0x79, 0x4f, 0x5e, 0xdf, 0xe8, 0xa3, 0xb2, 0x9d, 0xe0, 0xf8, 0xa8, 0xbe, 0xc1, 0x20, 0x5b, 0x40, 0x50, 0xec, 0x59, 0x1a, 0xb0, 0xf4, 0x6f, 0xc5, 0x7f, 0xfb, 0x05, 0x2b, 0xd5, 0xd6, 0xbc, 0x40, 0x42, 0xc5, 0x62, 0x74, 0xb6, 0xbb, 0xec, 0x68, 0xcc, 0x7e, 0x98, 0x1f, 0x4b, 0x1c, 0x12, 0xac, 0x00, 0x37, 0xdf, 0x90, 0x11, 0x6c, 0x3b, 0x1b, 0x74, 0x50, 0x41, 0x72, 0x1d, 0x8e, 0x1c, 0x55, 0x35, 0xb7, 0x06, 0xad, 0x68, 0xb9, 0xf7, 0xfc, 0xe4, 0xac, 0x23, 0xd9, 0x41, 0x3f, 0x7f, 0x43, 0xae, 0x82, 0x00, 0xb3, 0xd8, 0x0f, 0xa7, 0xb1, 0x50, 0x13, 0xf9, 0x7e, 0x26, 0x85, 0xb7, 0xdf, 0x62, 0x44, 0xd1, 0x5a, 0x86, 0xa4, 0x8f, 0xbe, 0xb8, 0x9f, 0xb1, 0xbd, 0x90, 0x2d, 0xc5, 0x73, 0xa8, 0xa7, 0x24, 0xff, 0xdf, 0xa2, 0x17, 0x9a, 0xbe, 0x57, 0xec, 0x4f, 0x21, 0xbb, 0x1f, 0x20, 0x2d, 0x05, 0xf8, 0xe2, 0x66, 0xe4, 0x5b, 0x3a, 0xf7, 0xfe, 0x41, 0x5e, 0x83, 0xeb, 0xb0, 0x78, 0x08, 0xef, 0xcf, 0x98, 0x67, 0x12, 0xe7, 0xff, 0xf6, 0xd9, 0xe0, 0xe4, 0xba, 0xfc, 0x2a, 0x17, 0xd3, 0xb7, 0x1b, 0x04, 0x5c, 0x02, 0x5f, 0xd7, 0xc8, 0x4b, 0xfd, 0x82, 0x80, 0xfc, 0xc6, 0x63, 0xe6, 0x7b, 0x98, 0xfa, 0xfb, 0xfb, 0x5d, 0x0d, 0x13, 0xce, 0x1a, 0xbc, 0x81, 0x9f, 0x1a, 0x0a, 0xfc, 0xea, 0xc9, 0x09, 0xd0, 0xfc, 0x5d, 0x02, 0x5e, 0xc9, 0xd8, 0xd0, 0x51, 0xd7, 0x5f, 0x5e, 0x24, 0x4c, 0x65, 0x0f, 0xa0, 0x51, 0x91, 0x79, 0xa5, 0x88, 0x5f, 0xc1, 0x88, 0x26, 0xd8, 0x0e, 0xc7, 0x28, 0x6a, 0x83, 0x71, 0xb2, 0x02, 0xec, 0xdf, 0xea, 0x71, 0x72, 0x90, 0x6c, 0x31, 0x08, 0x0b, 0x41, 0x03, 0xd9, 0x9a, 0xaf, 0x6d, 0xf5, 0x9c, 0xfb, 0x5f, 0x81, 0x20, 0x39, 0xde, 0xa8, 0xdb, 0xff, 0x8d, 0x2f, 0x37, 0x61, 0xec, 0x40, 0x24, 0xb1, 0xf2, 0xee, 0x82, 0x85, 0xd9, 0xf8, 0x3d, 0x11, 0xe0, 0xf0, 0x16, 0xd3, 0xe9, 0x77, 0x3c, 0x32, 0x16, 0xad, 0xf5, 0xaa, 0xe8, 0xbf, 0xc0, 0xb0, 0x6a, 0x0e, 0x30, 0x89, 0xf1, 0x81, 0x1e, 0xfd, 0xb4, 0xd8, 0xef, 0xe5, 0x15, 0xb0, 0x28, 0xca, 0x4d, 0xa1, 0x7b, 0x01, 0xf1, 0x21, 0xae, 0xf5, 0xf5, 0xfc, 0x30, 0xed, 0xfe, 0x08, 0x01, 0x6e, 0x9f, 0xda, 0x2a, 0x78, 0x92, 0x68, 0x0f, 0x87, 0x71, 0x4f, 0xff, 0xd1, 0x70, 0xd4, 0x28, 0x7c, 0x3e, 0xff, 0xd8, 0x24, 0x0a, 0x47, 0x46, 0xc6, 0xbc, 0x72, 0x7b, 0xf2, 0xe3, 0x2f, 0x77, 0xf2, 0x79, 0x42, 0xbe, 0x5c, 0x3d, 0x87, 0x53, 0xe8, 0xfe, 0xf4, 0x7f, 0xc8, 0x1d, 0xb4, 0x98, 0x6e, 0x79, 0xdd, 0x86, 0xe4, 0xb0, 0xcb, 0x0e, 0xb6, 0xff, 0xcc, 0x09, 0x3c, 0x40, 0x9f, 0x41, 0xde, 0xc5, 0xc2, 0x00, 0xa6, 0xdc, 0x14, 0x7c, 0x6c, 0xbf, 0xe3, 0x98, 0x21, 0x77, 0x0d, 0x82, 0x13, 0xd7, 0xa7, 0xf8, 0x62, 0x08, 0x6f, 0xec, 0xf2, 0x9e, 0xad, 0xff, 0xf0, 0x4d, 0xe3, 0xa0, 0xa1, 0xa2, 0x9c, 0x20, 0x0f, 0x78, 0x21, 0x04, 0x1d, 0x76, 0x2a, 0x7a, 0xd7, 0xd7, 0xf0, 0xa8, 0x5f, 0x38, 0x63, 0x19, 0x13, 0x46, 0x51, 0xec, 0xfd, 0x15, 0x17, 0x93, 0x30, 0x43, 0x04, 0xad, 0x0f, 0xef, 0x3c, 0x4a, 0xc1, 0xf2, 0x6f, 0xe2, 0x43, 0x84, 0xd7, 0xc9, 0x04, 0xfe, 0x70, 0x80, 0x36, 0x05, 0x6f, 0xbc, 0x20, 0x08, 0x36, 0xb9, 0x97, 0xeb, 0x62, 0x7f, 0x7f, 0xd0, 0xd0, 0xdf, 0x60, 0x75, 0xf6, 0x5f, 0x04, 0x22, 0x4f, 0x5a, 0x7f, 0xe1, 0xf2, 0xfc, 0xbd, 0x7b, 0x08, 0x09, 0xe9, 0xca, 0xb9, 0x3f, 0xa0, 0x88, 0x9a, 0x2e, 0xdc, 0xc1, 0x1e, 0x0a, 0x49, 0xf7, 0xd1, 0x70, 0x22, 0x8d, 0x3f, 0x5d, 0x1f, 0xf4, 0x33, 0xc6, 0x89, 0xd8, 0x71, 0xc0, 0x70, 0x38, 0x07, 0x6f, 0x90, 0x57, 0x62, 0xe5, 0xce, 0x81, 0x68, 0x5e, 0x85, 0x40, 0x51, 0xd1, 0x37, 0xb4, 0x41, 0xb1, 0xf6, 0xfe, 0x62, 0xf3, 0x7c, 0x8a, 0xc7, 0x63, 0x05, 0x76, 0x23, 0x04, 0x6b, 0xf0, 0x68, 0x17, 0xcf, 0xec, 0x2d, 0xab, 0xbf, 0xd3, 0xe5, 0x37, 0x95, 0x88, 0x77, 0x0f, 0x04, 0x0e, 0x0b, 0x2f, 0xae, 0xc1, 0xfc, 0x0c, 0x02, 0x81, 0x00, 0xe4, 0xc0, 0xc4, 0x8c, 0x22, 0x46, 0x11, 0x18, 0x45, 0xfc, 0x10, 0x85, 0xa3, 0xc0, 0xa1, 0xbc, 0x20, 0x34, 0x1c, 0xaa, 0xab, 0xfb, 0x2f, 0x9e, 0xab, 0xfd, 0x51, 0x3b, 0x13, 0xe2, 0x1d, 0x0f, 0x6f, 0xcb, 0xe4, 0x0d, 0x3e, 0x6b, 0x0e, 0xc5, 0xd8, 0xd1, 0xf7, 0x6b, 0x7d, 0x8a, 0x50, 0xc0, 0x63, 0xa9, 0x8e, 0x04, 0x13, 0x6c, 0x5e, 0x81, 0x48, 0x6f, 0xb0, 0x1f, 0xbd, 0xff, 0x82, 0x8f, 0x0a, 0x1e, 0xbe, 0xbf, 0x60, 0x90, 0x13, 0xec, 0x0b, 0x9c, 0x20, 0x0c, 0xac, 0x33, 0xc1, 0x09, 0xb2, 0x85, 0xbe, 0x24, 0x10, 0xf6, 0xbf, 0x76, 0x20, 0xbc, 0xcb, 0xf4, 0x08, 0x01, 0x0c, 0xdf, 0xeb, 0xbf, 0x41, 0x05, 0x7e, 0xa1, 0x7f, 0x4f, 0xaa, 0xd7, 0xbf, 0xc1, 0x20, 0x9f, 0x5e, 0x87, 0xd0, 0x21, 0x13, 0x9c, 0x2d, 0xc6, 0x53, 0x2c, 0x0b, 0xd0, 0x10, 0x49, 0xdf, 0xa2, 0x13, 0xd7, 0x82, 0xd0, 0xdf, 0xa4, 0xc3, 0xbf, 0xf8, 0x77, 0x18, 0x3c, 0x5c, 0x09, 0xda, 0xe6, 0x0c, 0x0e, 0xff, 0x4e, 0x8b, 0xc2, 0xa3, 0xf5, 0xf6, 0x1f, 0x5f, 0x19, 0xe0, 0x51, 0x04, 0x3e, 0xdb, 0x78, 0x2d, 0xf1, 0x82, 0x73, 0xf6, 0xaa, 0x95, 0x89, 0x97, 0xe0, 0x49, 0x15, 0x60, 0x61, 0x3f, 0xd8, 0xa1, 0xc9, 0x37, 0xfc, 0xbe, 0x50, 0x8f, 0xb0, 0x1f, 0x79, 0x10, 0x48, 0x9e, 0x81, 0x07, 0x85, 0x7c, 0x48, 0x30, 0xdf, 0xd9, 0xd5, 0x3f, 0xfe, 0x2a, 0x91, 0x02, 0x3b, 0x9d, 0x05, 0x00, 0xf6, 0x5f, 0x08, 0x06, 0x66, 0xd9, 0x82, 0x36, 0x7e, 0x9f, 0xe5, 0xdd, 0x3f, 0x82, 0x3b, 0xf8, 0xbc, 0x1e, 0x05, 0x7a, 0x5b, 0x5a, 0xb7, 0xff, 0x82, 0xd0, 0xb7, 0xa0, 0xf5, 0xaf, 0xeb, 0xe3, 0x43, 0xf9, 0x03, 0x7c, 0x39, 0x24, 0x20, 0xed, 0x4b, 0x2f, 0xd7, 0xdf, 0xe8, 0x39, 0xc3, 0x70, 0xe5, 0x0f, 0xc5, 0xbf, 0xf8, 0x13, 0x48, 0x19, 0xdd, 0x2b, 0x0e, 0xff, 0xc6, 0x89, 0xc7, 0x53, 0x1a, 0x73, 0x86, 0x03, 0x23, 0x78, 0x40, 0x23, 0x2a, 0x70, 0xc5, 0x3d, 0xed, 0xf0, 0x7f, 0x87, 0x01, 0x24, 0xf9, 0x60, 0x5d, 0x78, 0x91, 0x5e, 0x32, 0x0f, 0x1f, 0x0e, 0x48, 0x9c, 0x14, 0x9e, 0xba, 0x7f, 0xe0, 0xc3, 0xc3, 0xbd, 0x83, 0x13, 0x6c, 0x7e, 0xc2, 0x20, 0xaa, 0xfe, 0x70, 0xc3, 0xf1, 0xa4, 0xea, 0xe2, 0x88, 0x16, 0xf5, 0xb7, 0xf5, 0x5a, 0xff, 0x0f, 0x08, 0x3d, 0x8d, 0xed, 0x7f, 0x13, 0xe0, 0x97, 0xb0, 0x72, 0x27, 0xa5, 0x1a, 0x4c, 0x73, 0xc8, 0x08, 0x04, 0xe3, 0x16, 0x30, 0x4a, 0x19, 0xa7, 0x15, 0xe7, 0x1c, 0x20, 0x30, 0xf5, 0xb7, 0xff, 0xa9, 0x81, 0xc5, 0x11, 0xf5, 0xf9, 0x72, 0x08, 0x0d, 0xae, 0x14, 0x13, 0xa5, 0x62, 0x90, 0xc7, 0xe5, 0x15, 0xd8, 0xce, 0xc8, 0xe9, 0xae, 0xf0, 0xc7, 0xe1, 0x4d, 0x78, 0xe4, 0x55, 0xbe, 0xd0, 0xe9, 0x52, 0xc7, 0xe8, 0x80, 0x0e, 0x72, 0x74, 0x33, 0xc2, 0x27, 0xae, 0xff, 0xe1, 0x51, 0x5e, 0x95, 0x87, 0xee, 0x37, 0xda, 0xa5, 0xef, 0x41, 0x25, 0x1f, 0xb8, 0xff, 0x5f, 0xa0, 0x51, 0x87, 0x70, 0x8a, 0x53, 0x23, 0xeb, 0xb1, 0xa2, 0x3c, 0x3e, 0xd8, 0x18, 0x05, 0x02, 0x06, 0x58, 0x1c, 0x48, 0xc2, 0x17, 0x1b, 0x98, 0xbc, 0x5d, 0xbe, 0x0c, 0x44, 0xe0, 0xfe, 0x73, 0x5f, 0x3d, 0x6b, 0xfa, 0xaf, 0x85, 0x0d, 0xef, 0xc8, 0x26, 0xc0, 0x71, 0xe0, 0x4f, 0x9a, 0xf2, 0x7e, 0x3f, 0xc6, 0x82, 0x1f, 0x0c, 0xbb, 0x9e, 0xbc, 0x41, 0xac, 0x07, 0x30, 0x43, 0xd0, 0x50, 0x15, 0x74, 0xba, 0x2b, 0x13, 0x4d, }, { // nolint: dupl 0x80, 0x60, 0x75, 0x2d, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x01, 0xe9, 0x87, 0x82, 0x80, 0xcf, 0x63, 0xaf, 0xef, 0xd9, 0x41, 0x5d, 0x8a, 0x3e, 0xbd, 0x52, 0xab, 0x2e, 0xe8, 0xa7, 0xde, 0x0a, 0x41, 0x2d, 0xdf, 0xbe, 0x9d, 0x72, 0xfc, 0x21, 0xbf, 0xbd, 0x01, 0xca, 0xa3, 0xca, 0x2b, 0x32, 0xf1, 0xb1, 0x0d, 0x63, 0x3f, 0x10, 0x11, 0xeb, 0x4d, 0x7a, 0xfc, 0x22, 0x27, 0x87, 0x16, 0xa3, 0xdd, 0x8d, 0x8f, 0x94, 0xb9, 0xa3, 0xf0, 0x20, 0x82, 0x4f, 0x73, 0x2f, 0x87, 0xc4, 0xbe, 0xbd, 0x88, 0x08, 0xdf, 0xf3, 0xf6, 0xc8, 0xfc, 0xd3, 0xe4, 0xe5, 0x04, 0x81, 0x6b, 0xc7, 0x53, 0x1b, 0x51, 0x4c, 0x10, 0x17, 0x5f, 0x7a, 0xf6, 0x50, 0xcf, 0x7a, 0xb7, 0xff, 0x94, 0x77, 0x8f, 0x01, 0xdb, 0xfe, 0x0f, 0xe1, 0x10, 0x4f, 0xbf, 0x6b, 0x8b, 0xc3, 0x81, 0x0b, 0xf9, 0x84, 0x17, 0xd0, 0xfe, 0xc2, 0xa2, 0xac, 0x9c, 0x30, 0x94, 0xe6, 0xae, 0x05, 0xd6, 0xfc, 0x61, 0xeb, 0xbf, 0xfc, 0x12, 0x6d, 0x6d, 0xe0, 0x50, 0x27, 0x6b, 0xc6, 0x02, 0x6d, 0x16, 0x6c, 0xd0, 0x9f, 0xa0, 0xe8, 0x2c, 0xf6, 0xf2, 0x84, 0x01, 0xfd, 0x34, 0x7f, 0x90, 0x57, 0x6e, 0xd9, 0xc3, 0x06, 0xde, 0xc8, 0x6f, 0x32, 0xfc, 0xa0, 0xbe, 0xfd, 0x9f, 0xaf, 0xeb, 0xe5, 0x0b, 0x53, 0xfd, 0xc9, 0x69, 0xff, 0xc1, 0x07, 0x8c, 0x05, 0x5a, 0xf2, 0x04, 0x0c, 0xa1, 0x79, 0xd9, 0x9d, 0x77, 0x3a, 0x1a, 0x17, 0xf0, 0xf6, 0xd9, 0xd2, 0x8f, 0x02, 0xaa, 0xff, 0x5f, 0x0c, 0xd0, 0xfd, 0x7d, 0x7f, 0x82, 0x8e, 0x97, 0x7d, 0x71, 0xd3, 0xd5, 0xbf, 0xfa, 0x04, 0x22, 0xfd, 0xee, 0xfd, 0x87, 0x05, 0xf9, 0x84, 0x3a, 0x5d, 0x90, 0x4e, 0xf5, 0x16, 0x3e, 0x34, 0x9b, 0xa0, 0xe9, 0x2f, 0xf0, 0xe0, 0x11, 0x02, 0xdb, 0xf7, 0xe5, 0xcf, 0x4f, 0xf2, 0x86, 0x6b, 0x5e, 0x54, 0xff, 0xec, 0x60, 0x9d, 0x8b, 0xa0, 0x34, 0x2f, 0x28, 0xec, 0x68, 0x1e, 0x6a, 0xce, 0xfe, 0xf5, 0xc7, 0x3a, 0x03, 0xe4, 0x15, 0x42, 0xd9, 0x31, 0x20, 0x1c, 0x82, 0x7c, 0x48, 0x5f, 0xe3, 0x60, 0xab, 0x35, 0x62, 0x59, 0x68, 0x6d, 0x7e, 0x8b, 0xe3, 0x41, 0x17, 0xa3, 0xaa, 0xc3, 0x80, 0x93, 0xd2, 0xd7, 0x41, 0x40, 0xbe, 0xc0, 0x76, 0xf6, 0x2f, 0xba, 0xff, 0x62, 0x05, 0x7d, 0x85, 0x0e, 0x26, 0xed, 0x9f, 0x45, 0x5c, 0x38, 0x7c, 0xaa, 0x54, 0xe1, 0xf0, 0x20, 0x74, 0x0a, 0x0b, 0x6f, 0xf2, 0x82, 0x1a, 0x7b, 0x1f, 0x72, 0x82, 0xd0, 0xff, 0xce, 0xcc, 0x74, 0x1c, 0xac, 0x5a, 0xfd, 0xbb, 0xff, 0x61, 0x13, 0xd5, 0xbf, 0xea, 0x8a, 0xfb, 0x5e, 0x34, 0xbc, 0x81, 0x80, 0xf8, 0x90, 0xb5, 0xc6, 0xc4, 0xad, 0x02, 0xbc, 0xe1, 0xb5, 0xf6, 0xff, 0x10, 0x17, 0xe8, 0x0b, 0x36, 0x6b, 0xfd, 0xf5, 0x0b, 0xcc, 0x1b, 0xb1, 0xe5, 0x52, 0x26, 0x26, 0xbd, 0x9f, 0xd3, 0xe4, 0x2a, 0xd7, 0x27, 0xc2, 0x9e, 0x30, 0x13, 0xeb, 0xe9, 0xa7, 0xde, 0x24, 0x10, 0x50, 0x16, 0x86, 0x7e, 0xbc, 0x33, 0x17, 0x6b, 0x47, 0x17, 0x4b, 0xd7, 0xf6, 0x5e, 0x5f, 0x87, 0xa5, 0x43, 0xeb, 0x7a, 0xb2, 0x76, 0x7f, 0xdf, 0x60, 0xf4, 0x2d, 0x82, 0x85, 0x22, 0x03, 0xa4, 0x93, 0x6e, 0xd8, 0xf2, 0xfe, 0xf5, 0xd7, 0xba, 0x28, 0x73, 0x61, 0xf8, 0x76, 0xff, 0xc2, 0x20, 0xa3, 0xbf, 0xa0, 0x9f, 0xc6, 0x02, 0xe9, 0x03, 0x01, 0xb1, 0xdb, 0xee, 0xf0, 0x34, 0x82, 0x1d, 0x0f, 0xeb, 0xc6, 0x85, 0xfa, 0x1e, 0xde, 0xbd, 0x8b, 0xf6, 0x0d, 0x43, 0x33, 0xfe, 0x5b, 0x5f, 0xfc, 0x4f, 0x33, 0x16, 0x3a, 0x23, 0xaa, 0xd1, 0x05, 0xe6, 0x55, 0xec, 0x5e, 0x41, 0x3e, 0xf6, 0xff, 0x0d, 0x77, 0x75, 0xfd, 0x3f, 0x87, 0x7b, 0x96, 0x0d, 0xfa, 0xb4, 0x35, 0x62, 0x63, 0xef, 0xf9, 0x01, 0x05, 0x63, 0x50, 0x0f, 0x98, 0x40, 0x67, 0xba, 0x50, 0xaf, 0xfa, 0x12, 0x08, 0xbd, 0x7b, 0xb2, 0x84, 0x3d, 0xb6, 0xb4, 0x1e, 0xdf, 0x43, 0x02, 0xf6, 0x56, 0xad, 0xed, 0xce, 0x2b, 0xf4, 0xf8, 0x80, 0xb7, 0xad, 0x73, 0x82, 0x9f, 0xfd, 0x07, 0x4f, 0x5d, 0xfa, 0x7f, 0x1b, 0xb5, 0xd8, 0x1b, 0x4c, 0x05, 0xda, 0xd8, 0x0e, 0x7d, 0xf7, 0xfe, 0x2b, 0xd0, 0xb4, 0x23, 0x2f, 0xe0, 0x8b, 0xa7, 0xee, 0x81, 0x28, 0x22, 0xbd, 0xef, 0xd0, 0x20, 0x0c, 0x77, 0xf7, 0x4a, 0x1f, 0xb7, 0xe1, 0x1f, 0x18, 0x3f, 0x7f, 0x19, 0x19, 0xf8, 0x39, 0x92, 0x20, 0x3f, 0x15, 0x6b, 0xca, 0x1f, 0xa1, 0x6c, 0x40, 0x2d, 0xb2, 0xfa, 0x0e, 0xe3, 0x03, 0xa0, 0x12, 0x5f, 0xa5, 0x87, 0x00, 0x8a, 0x0c, 0x3d, 0xf5, 0x0c, 0xfe, 0x97, 0xef, 0x7e, 0x1c, 0x13, 0xb0, 0xf3, 0x37, 0xf1, 0x00, 0x94, 0x21, 0xec, 0x5a, 0x1d, 0xbf, 0xaf, 0x28, 0x90, 0x4d, 0xd8, 0xa8, 0x2b, 0x1d, 0xdd, 0x0c, 0x0e, 0xca, 0x18, 0x18, 0xf0, 0x97, 0xd1, 0xd0, 0x3f, 0x3c, 0x0a, 0x9e, 0xff, 0xcb, 0xe0, 0xa4, 0x17, 0x5e, 0xd7, 0xa6, 0xe7, 0xc6, 0x7b, 0xd8, 0x18, 0xf0, 0x8f, 0xd8, 0xa6, 0x0d, 0x26, 0x6f, 0xdf, 0xf1, 0x5b, 0x24, 0x34, 0x82, 0x07, 0x4b, 0xec, 0x67, 0xcd, 0xbf, 0xb0, 0x24, 0x89, 0xf7, 0x6f, 0xe6, 0x04, 0x80, 0x83, 0xd2, 0xdf, 0xbd, 0xdf, 0x7f, 0xa1, 0x03, 0x34, 0x03, 0xdf, 0xec, 0xb9, 0x57, 0xe8, 0x81, 0xed, 0xbf, 0x21, 0x97, 0x40, 0x74, 0xfe, 0xb7, 0xfb, 0xec, 0x48, 0x4f, 0x2e, 0x6d, 0x51, 0xf0, 0xe8, 0x65, 0x70, 0xe3, 0xc3, 0x9d, 0x14, 0x4e, 0xd9, 0x95, 0xe9, 0xc9, 0x1e, 0x84, 0x89, 0xe6, 0xed, 0x8f, 0x40, 0xf7, 0x04, 0x3e, 0x20, 0x17, 0xe3, 0x00, 0xf1, 0xc0, 0x1b, 0xf5, 0x57, 0xff, 0xcb, 0x20, 0x6e, 0x60, 0xbf, 0x1a, 0x1d, 0x04, 0x76, 0x1c, 0x7d, 0x17, 0x3b, 0x78, 0x20, 0x0d, 0x76, 0xea, 0xff, 0xf8, 0x74, 0x97, 0xfb, 0x12, 0x0b, 0x38, 0xf0, 0xe9, 0xfb, 0x70, 0x56, 0x1d, 0xfe, 0x2b, 0xb5, 0x2a, 0x09, 0xc3, 0x7e, 0x1d, 0x04, 0x9e, 0xfd, 0xc3, 0xc4, 0x3d, 0x5b, 0xff, 0xe1, 0xbf, 0x4f, 0xa9, 0xed, 0xfe, 0xc1, 0x01, 0xea, 0xbf, 0xf3, 0x82, 0x90, 0xc5, 0xc7, 0x84, 0x8e, 0xd9, 0x95, 0xe9, 0x8f, 0xba, 0xff, 0x41, 0xc1, 0xfa, 0x52, 0xa9, 0xc7, 0x21, 0x3b, 0x10, 0x46, 0x86, 0x3f, 0x41, 0x03, 0xd7, 0xb5, 0xeb, 0x21, 0xeb, 0xdf, 0xf8, 0x40, 0x2d, 0x42, 0x56, 0x07, 0x4f, 0xaf, 0xe8, 0x97, 0x71, 0x34, 0x05, 0x59, 0x33, 0x20, 0x60, 0xfe, 0x24, 0x13, 0xfb, 0x1c, 0x6c, 0x42, 0x77, 0xec, 0x68, 0x20, 0xc4, 0xa1, 0xa4, 0xf9, 0xef, 0xd3, 0x0f, 0xd9, 0xf9, 0xe1, 0xb9, 0x84, 0x06, 0xc0, 0xab, 0xaf, 0xf1, 0x04, 0xf0, 0xc7, 0x41, 0x10, 0x4f, 0x67, 0xcb, 0x91, 0x9a, 0x6c, 0xec, 0x80, 0xbe, 0x81, 0x16, 0x63, 0xa1, 0x65, 0x0c, 0x63, 0x08, 0xe5, 0xaf, 0xfc, 0x09, 0x1e, 0x40, 0xfe, 0xdd, 0x9d, 0x9f, 0x97, 0xeb, 0xf6, 0x7e, 0x62, 0x87, 0xe2, 0xe6, 0x0b, 0xfa, 0xa1, 0xec, 0xa3, 0xe0, 0x94, 0xdd, 0x51, 0x4d, 0x4b, 0xc8, 0x4d, 0x8b, 0xdc, 0x10, 0x78, 0xf0, 0x95, 0xaf, 0x67, 0x41, 0xaf, 0xbf, 0xe5, 0xf0, 0xa0, 0x6f, 0x5d, 0x77, 0xfb, 0xc2, 0x2a, 0xfd, 0x07, 0x1f, 0x7e, 0xca, 0x3f, 0x38, 0x80, 0xd8, 0xf1, 0x90, 0xf0, 0x1d, 0xe9, 0x72, 0xde, 0x11, 0x08, 0x92, 0xc4, 0x83, 0x87, 0x25, 0xb0, 0xee, 0x10, 0xf1, 0x5b, 0x28, 0x33, 0x40, 0xd8, 0x3f, 0xc1, 0x3d, 0xb4, 0xc8, 0x25, 0x25, 0x9f, 0xbf, 0x53, 0x70, 0xd4, 0x37, 0x04, 0xb5, 0xee, 0x19, 0xd9, 0x32, 0x75, 0xf7, 0xfc, 0x60, 0x73, 0x5d, 0x69, 0x7f, 0xb2, 0x8a, 0xb0, 0xf2, 0x78, 0x61, 0x17, 0x5f, 0xca, 0x50, 0x5b, 0x89, 0x4a, 0xfc, 0x60, 0x66, 0xdf, 0xb8, 0x2b, 0xbc, 0x78, 0x40, 0xbc, 0xc2, 0x0e, 0xbc, 0xcb, 0x1b, 0x82, 0xee, 0xf1, 0xac, 0xbf, 0x63, 0xa1, 0x22, 0x76, 0x18, 0xe0, 0xef, 0xa1, 0x69, 0x7e, 0x1d, 0xf1, 0xd1, 0xc0, 0x60, 0x8c, 0xfe, 0xb9, 0x7a, 0xfc, 0x33, 0x8a, 0xed, 0xaf, 0xfa, 0xf9, 0x93, 0xe0, 0x83, 0xd0, 0x2b, 0xbd, 0x8e, 0x50, 0x80, 0x35, 0x6f, 0xff, 0x87, 0xb6, 0x90, 0x3e, 0x40, 0x41, 0xc7, 0x11, 0x3a, 0xfb, 0x7f, 0x41, 0x80, 0x4d, 0x7d, 0x7a, 0x1f, 0xbc, 0x81, 0x59, 0xa1, 0xf9, 0xbd, 0xcf, 0xd7, 0xe5, 0x13, 0xd0, 0xe8, 0x3c, 0x18, 0xe2, 0x3e, 0x88, 0x33, 0x98, 0x2c, 0xfb, 0x79, 0x0a, 0x69, 0x1a, 0x3c, 0x84, 0xb0, 0xe8, 0x10, 0x06, 0xeb, 0xac, 0xbb, 0xe3, 0xc4, 0x4f, 0x39, 0x25, 0xfd, 0x3e, 0x71, 0x03, 0xa9, 0x76, 0xde, 0x11, 0x1d, 0x2c, 0x38, 0xd4, 0x4a, 0xf2, 0x24, 0x07, 0xf8, 0xac, 0xe1, 0x00, 0xcc, 0x18, 0xef, 0xc8, 0x11, 0x1f, 0x68, 0x80, 0xfe, 0x8b, 0xaf, 0x64, 0x7e, 0xb9, 0x63, 0x66, 0x5c, 0x81, 0xc9, 0x03, 0x01, 0xd8, 0xb1, 0xf1, 0x27, 0xf8, 0xe4, 0x6d, 0x70, 0xc6, 0x8b, 0xfa, 0x12, 0x1e, 0xf2, 0xb3, 0x8d, 0x53, 0x66, 0x33, 0xd7, 0xef, 0xe5, 0x87, 0xfd, 0xa3, 0xfd, 0xb4, 0xb2, 0x92, 0x60, 0xf2, 0xf6, 0xff, 0x45, 0x04, 0x15, 0xf9, 0x8b, 0xa9, 0x64, 0x51, 0x26, 0x4b, 0x5f, 0xe9, 0xfb, 0xd1, 0x22, 0x18, 0xa6, 0x8a, 0x1d, 0xc6, 0xc5, 0xcd, 0xe1, 0xf8, 0x98, 0xed, 0xc7, 0x4f, 0xb5, 0x26, 0xdd, 0x7b, 0xff, 0x20, 0x4b, 0x6f, 0x74, 0xbd, 0x41, 0x66, 0xd5, 0x2a, 0x58, 0xe8, 0x23, 0x21, 0xc7, 0xef, 0x67, 0xee, 0xe0, 0xae, 0xf1, 0x92, 0xfd, 0xb4, 0xb1, 0xa9, 0xfd, 0x2d, 0x87, 0x2e, 0x09, 0x40, 0x82, 0x7d, 0x69, 0xff, 0xe3, 0x29, 0xf6, 0xb6, 0x16, 0xfa, 0x01, 0xd7, 0x89, 0x2c, 0x6c, 0x25, 0x66, 0xae, 0x13, 0x2f, 0x42, 0x03, 0xfa, 0xa5, 0x69, 0x94, 0xc3, 0x59, 0x49, 0x1a, 0xfb, 0xfd, 0xbc, 0x21, 0x0c, 0x79, 0x5d, 0xc8, 0x0c, 0xef, 0xf6, 0xf4, 0x24, 0x17, 0xe5, 0x52, 0xc1, 0xf1, 0xa8, 0x86, 0x0b, 0x63, 0xfa, 0x7c, 0xa2, 0x01, 0x66, 0x55, 0x54, 0x7c, 0x6d, 0x0f, 0x80, 0x2b, 0x0c, 0x38, 0x40, 0xcf, 0xb2, 0x82, 0x3c, 0x92, 0x1f, 0xf8, 0x25, 0x59, 0x78, 0x70, 0x15, 0xcd, 0x1c, 0xa2, 0x03, 0xc7, 0x53, 0x03, 0x8b, 0xa7, 0x42, 0x5b, 0xf7, 0x90, 0x7d, 0xac, 0x87, 0x52, 0xd5, 0x5e, 0x3d, 0xd9, 0x50, 0x3f, 0xc8, 0x08, 0x3b, 0xf0, 0xfa, 0x9c, 0x53, 0xd9, 0xfa, 0xff, 0xf1, 0xc3, 0x41, 0x07, 0x93, 0x73, }, { // nolint: dupl 0x80, 0x60, 0x75, 0x2e, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x01, 0x61, 0xb2, 0x82, 0x41, 0x92, 0x4b, 0xdb, 0xe5, 0xfc, 0x8f, 0x60, 0x47, 0x0c, 0x1f, 0x51, 0xdb, 0x29, 0x01, 0x00, 0x61, 0x79, 0x43, 0x02, 0xbe, 0x9f, 0x87, 0x39, 0x60, 0xaf, 0x7f, 0xb4, 0xf2, 0xc7, 0xdd, 0x10, 0x3f, 0xcb, 0x90, 0x7c, 0x41, 0x1b, 0x6b, 0xf9, 0x07, 0xe3, 0xbb, 0xb7, 0xd3, 0xe8, 0xa7, 0xf6, 0xff, 0xee, 0x1c, 0xf0, 0x97, 0x94, 0xb2, 0xbf, 0x45, 0xf1, 0x80, 0xbf, 0x9d, 0x24, 0x3c, 0x95, 0xde, 0xc0, 0xe7, 0x31, 0xfd, 0x3e, 0x11, 0x0a, 0x87, 0xfa, 0xfa, 0x68, 0x6f, 0x1f, 0x90, 0x83, 0x87, 0x59, 0x7f, 0xb6, 0xff, 0xd5, 0x7b, 0xb2, 0x02, 0xdd, 0x0f, 0xa3, 0x75, 0xf7, 0x30, 0x44, 0x6e, 0xfc, 0x0e, 0xac, 0x6b, 0xb4, 0x31, 0xe8, 0x02, 0x70, 0xb5, 0xf5, 0xfa, 0x3f, 0x83, 0x43, 0xc8, 0xd1, 0x7f, 0xfc, 0x9e, 0x40, 0x4b, 0x77, 0xef, 0xd1, 0x69, 0xca, 0x13, 0x92, 0x8d, 0xa4, 0x18, 0x8b, 0x30, 0x4f, 0x8c, 0x5e, 0xcc, 0x50, 0x49, 0xd6, 0xef, 0x20, 0x73, 0xb0, 0xad, 0xeb, 0xf9, 0x06, 0x84, 0x3d, 0xb5, 0xf5, 0xa0, 0x7b, 0x28, 0x26, 0x96, 0x30, 0x07, 0x4c, 0xa1, 0x00, 0xfe, 0x6f, 0x2f, 0x94, 0x39, 0xef, 0xdf, 0xeb, 0xa1, 0x01, 0xee, 0xf7, 0xa1, 0x21, 0xc2, 0x25, 0x82, 0xb2, 0xeb, 0xf9, 0xe0, 0xbe, 0x5f, 0x49, 0x9f, 0x9b, 0xd9, 0xe6, 0x08, 0x87, 0xfd, 0x96, 0xc3, 0x56, 0xfd, 0x7f, 0xa7, 0xb2, 0x84, 0xb3, 0xfe, 0x9f, 0xc3, 0x9e, 0x11, 0xeb, 0x58, 0x80, 0xd5, 0x96, 0xda, 0xdb, 0xff, 0x89, 0x17, 0xdf, 0xa5, 0xd1, 0x03, 0x95, 0xeb, 0xfd, 0x3c, 0xd1, 0xf4, 0xb6, 0x09, 0xe5, 0x2d, 0x9e, 0xff, 0x0c, 0x53, 0x4b, 0x60, 0xca, 0x4b, 0xaf, 0xf6, 0xf0, 0x8c, 0x23, 0x5a, 0x06, 0xf0, 0x1d, 0xe8, 0xd4, 0xf9, 0xcc, 0x7f, 0x0e, 0xf3, 0x0c, 0x25, 0x8b, 0x48, 0x35, 0xd5, 0xdb, 0xd6, 0x9f, 0xf0, 0x8e, 0x20, 0x7e, 0x99, 0x26, 0xbf, 0xf9, 0x3b, 0x1f, 0x40, 0x40, 0x0c, 0x57, 0xef, 0x5f, 0xef, 0x90, 0x80, 0x97, 0x98, 0x1d, 0x77, 0x8a, 0x83, 0xb7, 0x44, 0x05, 0x11, 0x92, 0x27, 0xd0, 0x34, 0x0a, 0x40, 0x41, 0x67, 0x14, 0x41, 0x73, 0x8f, 0xe0, 0x43, 0xf3, 0x0c, 0x1b, 0x07, 0xf7, 0x05, 0xba, 0x06, 0x80, 0x54, 0x03, 0xf9, 0x79, 0x03, 0x5a, 0x59, 0x27, 0x5a, 0x76, 0xbf, 0x06, 0x03, 0xf7, 0xdf, 0xf6, 0xf6, 0x41, 0xbe, 0x98, 0xc8, 0x3c, 0xf0, 0x5e, 0xd2, 0x46, 0xcd, 0x79, 0x7f, 0xec, 0xa1, 0x1f, 0x60, 0x65, 0x25, 0x18, 0x08, 0x1c, 0x4c, 0x11, 0x37, 0xc4, 0x8c, 0x0a, 0xc6, 0x01, 0xca, 0x3b, 0xbb, 0x7e, 0xac, 0xfa, 0x7f, 0x95, 0x5c, 0xf8, 0x22, 0xa7, 0x76, 0x0f, 0x0f, 0x8c, 0xdf, 0x4b, 0xd8, 0x22, 0x92, 0xb2, 0xec, 0xf2, 0x4b, 0xe0, 0xbe, 0x3e, 0xb8, 0x82, 0x8b, 0xca, 0x4b, 0xdb, 0xec, 0x80, 0xa7, 0x0f, 0x5c, 0x69, 0x12, 0x6b, 0xf3, 0xf7, 0xef, 0x7e, 0xc1, 0x67, 0x40, 0x4c, 0x0c, 0xda, 0x4f, 0x54, 0xff, 0xe0, 0x80, 0x10, 0x02, 0x9d, 0x97, 0x18, 0xdc, 0x3f, 0xb7, 0xbf, 0xc4, 0xfc, 0xc4, 0x63, 0x60, 0x84, 0xfb, 0x86, 0x38, 0xf0, 0x59, 0xf9, 0x49, 0x2e, 0xdf, 0xfc, 0x3f, 0x61, 0x7e, 0x71, 0x2c, 0x98, 0x41, 0x01, 0x56, 0xff, 0xe7, 0x20, 0xef, 0x40, 0xd0, 0x3a, 0x4d, 0x8d, 0x05, 0x8f, 0xf8, 0xc5, 0xae, 0x11, 0x28, 0xc9, 0x73, 0xef, 0xdf, 0xb5, 0xe4, 0x0f, 0xec, 0x71, 0xa1, 0x30, 0xca, 0x8b, 0x22, 0x5a, 0x85, 0x31, 0x94, 0x5a, 0xb4, 0xff, 0xb2, 0x04, 0x7b, 0x1b, 0x3f, 0x38, 0x20, 0x8f, 0x03, 0xc1, 0xfe, 0xaf, 0xad, 0x51, 0x41, 0x55, 0xe1, 0x80, 0x81, 0xbb, 0x25, 0x3a, 0x2e, 0x18, 0xbf, 0xf6, 0xed, 0xf9, 0x76, 0x24, 0x23, 0xaf, 0xba, 0x0f, 0x20, 0xc2, 0x34, 0xd7, 0x08, 0x90, 0x6c, 0x61, 0xcc, 0xd3, 0x31, 0xf0, 0x76, 0xe3, 0xff, 0xff, 0x54, 0xc9, 0x1a, 0x75, 0x9a, 0xd2, 0x22, 0xb0, 0x84, 0x2f, 0xa7, 0xf0, 0xc8, 0x40, 0x10, 0x7b, 0x1b, 0x09, 0x1c, 0x86, 0x35, 0x08, 0x2f, 0x39, 0x2a, 0xdb, 0xe9, 0xbe, 0x61, 0xa0, 0x87, 0x31, 0x70, 0xf9, 0x8f, 0x1b, 0xb8, 0x4b, 0xde, 0xff, 0x08, 0x74, 0x40, 0xd6, 0x47, 0xc1, 0xc0, 0x71, 0x6a, 0x9b, 0xff, 0xca, 0x0a, 0xfc, 0x00, 0xc8, 0xdd, 0xf7, 0xaf, 0x77, 0xf8, 0x65, 0x0e, 0xde, 0xfc, 0x74, 0x74, 0x9b, 0x7e, 0x58, 0xcb, 0x31, 0x83, 0x38, 0x76, 0xdc, 0x14, 0x2f, 0xa0, 0x46, 0x05, 0xef, 0xc7, 0x90, 0x21, 0x52, 0xd6, 0x3f, 0xd1, 0xca, 0x48, 0x71, 0x92, 0x68, 0x6e, 0x5a, 0x82, 0x9d, 0xe9, 0x48, 0x68, 0x68, 0x54, 0xd9, 0x09, 0x50, 0x17, 0x27, 0xec, 0x09, 0x87, 0x91, 0xf1, 0x8e, 0xff, 0xf0, 0xca, 0x94, 0xb9, 0x4a, 0x10, 0xd2, 0xec, 0x0b, 0xc6, 0x81, 0xc2, 0xd2, 0x5a, 0x82, 0x0c, 0x82, 0xa3, 0xa6, 0xe6, 0x06, 0xe9, 0x8c, 0x3d, 0x22, 0xba, 0xd6, 0x6b, 0xf7, 0xbe, 0xc9, 0xcf, 0xe2, 0x0a, 0x1e, 0xca, 0x92, 0xc5, 0xa0, 0x6d, 0xb8, 0xf0, 0x60, 0xb7, 0x01, 0xae, 0xbf, 0xcc, 0x51, 0xb5, 0xbf, 0x0f, 0x53, 0x5d, 0xd7, 0x2e, 0x61, 0x86, 0x5b, 0x56, 0xd1, 0xc4, 0xce, 0x9e, 0xbf, 0x24, 0x65, 0xdb, 0xa5, 0xbe, 0xa8, 0x91, 0xe8, 0x9b, 0xce, 0x50, 0xfd, 0xfd, 0x04, 0x30, 0x3c, 0x3c, 0xac, 0x5a, 0x88, 0x8c, 0xbf, 0x6f, 0xee, 0x34, 0x12, 0x20, 0x26, 0x82, 0x7d, 0x90, 0xf5, 0xaf, 0xfc, 0xa7, 0xaa, 0xff, 0xe4, 0x15, 0x47, 0x43, 0x61, 0x5f, 0xd1, 0x01, 0x07, 0xb2, 0xa3, 0x35, 0xfd, 0xed, 0xfd, 0x3f, 0x85, 0x34, 0x55, 0xa0, 0x49, 0x74, 0x45, 0x25, 0xbb, 0xb1, 0xe5, 0x08, 0x69, 0x7b, 0xe5, 0x20, 0x90, 0xdf, 0x8c, 0x12, 0x1d, 0xee, 0xc8, 0x08, 0x9b, 0xd7, 0x9e, 0xff, 0x8a, 0xfc, 0xab, 0x7d, 0x9a, 0x74, 0xff, 0x04, 0x0a, 0x4b, 0xc0, 0x93, 0xe4, 0x04, 0x5b, 0x17, 0xdd, 0xc3, 0xd6, 0x0b, 0xed, 0x35, 0x6b, 0x4e, 0xbe, 0xbe, 0x78, 0x43, 0x47, 0xe1, 0x89, 0xed, 0xc5, 0xcc, 0x73, 0x51, 0x94, 0xb6, 0x0d, 0x03, 0x6b, 0x55, 0xf9, 0x49, 0xa0, 0x79, 0x44, 0x07, 0x7d, 0xba, 0xa1, 0x25, 0xb7, 0x4f, 0xf4, 0x5f, 0x42, 0x7c, 0xbf, 0x09, 0x53, 0xf4, 0x07, 0x93, 0x0e, 0x2b, 0x0d, 0x86, 0x3b, 0x11, 0xd4, 0x11, 0xed, 0x79, 0xfe, 0x7a, 0xbf, 0xff, 0x0c, 0xf6, 0xeb, 0xfa, 0x7c, 0x14, 0x7e, 0x2b, 0x7f, 0xbf, 0x9f, 0x1d, 0xfb, 0xf8, 0x1f, 0xca, 0x23, 0x49, 0x3a, 0xfc, 0x08, 0x20, 0x84, 0xf3, 0x35, 0xb1, 0x57, 0xd3, 0xf8, 0x10, 0x0f, 0x25, 0xfe, 0xbc, 0x48, 0x23, 0xe9, 0x5d, 0xc7, 0x97, 0xca, 0xb5, 0xcd, 0x0e, 0x6b, 0x6e, 0x55, 0xaf, 0xfd, 0x04, 0x0f, 0x21, 0x7f, 0x4f, 0xc6, 0x88, 0xed, 0x76, 0x7e, 0x47, 0xa2, 0xfc, 0xdb, 0xf8, 0x72, 0x1d, 0xd6, 0xca, 0x96, 0xdf, 0xa9, 0x6a, 0x7f, 0xfe, 0x2e, 0x19, 0xe2, 0x79, 0x12, 0xdf, 0xfc, 0x08, 0x50, 0x47, 0xb5, 0xd8, 0xe2, 0xe4, 0xe9, 0x3e, 0x40, 0x52, 0x4a, 0x5f, 0x38, 0x25, 0x7d, 0xac, 0x2b, 0x94, 0x20, 0x0f, 0xfd, 0x7b, 0x56, 0xf2, 0x14, 0xf5, 0xb7, 0xfe, 0x1b, 0x2f, 0x05, 0x53, 0xd8, 0xd1, 0xff, 0xc4, 0x84, 0xaf, 0xfb, 0x7c, 0x20, 0x09, 0x3d, 0xeb, 0xc8, 0x6b, 0xfe, 0x50, 0xed, 0xef, 0xef, 0xd2, 0xaa, 0x17, 0xfc, 0x28, 0x34, 0x15, 0xd8, 0x5a, 0xe7, 0xfd, 0xfd, 0x77, 0x04, 0x5b, 0x7c, 0x5c, 0x9e, 0xc2, 0x1e, 0x52, 0x57, 0xae, 0x1e, 0x86, 0x7c, 0x3c, 0xc0, 0xbf, 0xfc, 0x45, 0xd2, 0x76, 0xd3, 0xce, 0x04, 0x83, 0xf0, 0xe8, 0xff, 0xb0, 0xc0, 0x22, 0xaf, 0xf3, 0xc2, 0x21, 0xcb, 0xf5, 0xe9, 0xff, 0x2f, 0x62, 0x43, 0x5a, 0x79, 0x4b, 0x6f, 0xfc, 0x84, 0x0a, 0xf6, 0xb5, 0xc8, 0xbf, 0xdb, 0xf9, 0x42, 0xd2, 0x5d, 0x7d, 0xf9, 0x74, 0xff, 0xc1, 0x87, 0x20, 0x31, 0xf0, 0x24, 0xf9, 0x01, 0x17, 0x4b, 0x3d, 0xf1, 0x16, 0xd6, 0x1e, 0xbd, 0x63, 0x78, 0x91, 0x05, 0xf6, 0xf1, 0xe4, 0x04, 0x5d, 0x3c, 0x89, 0xc2, 0xb0, 0xcd, 0x82, 0x1c, 0x2e, 0x7b, 0x16, 0xf6, 0xff, 0x27, 0xc3, 0x9e, 0x25, 0x75, 0xe4, 0xf1, 0xbc, 0x2b, 0xec, 0x81, 0xaa, 0xf8, 0xba, 0x7d, 0x3e, 0x08, 0x88, 0x79, 0x12, 0x9f, 0xfc, 0x6c, 0xf5, 0x64, 0x9f, 0xfc, 0xbe, 0x15, 0x3d, 0x5b, 0xff, 0xc4, 0x74, 0x5f, 0x92, 0xbf, 0x86, 0xa1, 0x9a, 0x6c, 0xfa, 0xdf, 0xfd, 0x17, 0xaf, 0x84, 0x17, 0xbc, 0x0a, 0x8f, 0xd0, 0x7b, 0x96, 0xfd, 0x72, 0x58, 0xac, 0x5e, 0x19, 0x7d, 0x78, 0xe1, 0x20, 0x8f, 0x7d, 0x8a, 0xb8, 0x24, 0xec, 0xfd, 0xdc, 0x12, 0x6b, 0xb0, 0xf0, 0x95, 0x7e, 0xb5, 0x94, 0x11, 0x52, 0xee, 0x78, 0x19, 0x0d, 0xd7, 0x81, 0x00, 0x9e, 0x30, 0x11, 0x5a, 0xdd, 0x87, 0x1a, 0x7a, 0xfb, 0xfc, 0x64, 0x57, 0xad, 0x2f, 0x93, 0xc1, 0x49, 0xeb, 0xf4, 0xfd, 0xc5, 0x6b, 0xb7, 0xf1, 0xc5, 0x2d, 0x53, 0xf8, 0x2d, 0x11, 0xeb, 0xa7, 0x87, 0x71, 0xa1, 0xf0, 0xbd, 0x9b, 0x56, 0x74, 0x7b, 0x24, 0xff, 0x3b, 0xf4, 0xfc, 0x10, 0x4f, 0x5f, 0xef, 0x87, 0x60, 0xbf, 0xb2, 0xb2, 0xeb, 0xfb, 0x5c, 0x54, 0x12, 0x7a, 0x59, 0x70, 0xd4, 0x37, 0xbe, 0xb4, 0x7f, 0xee, 0x0a, 0xfd, 0x3e, 0xf5, 0xfb, 0x8b, 0x2b, 0xda, 0xf3, 0x05, 0x78, 0x81, 0xa0, 0x93, 0xd2, 0x97, 0x28, 0x90, 0x9d, 0x3f, 0xd3, 0xc3, 0xc2, 0x03, 0xbd, 0x97, 0xa6, 0xd7, 0x5e, 0x9f, 0xe0, 0x96, 0x15, 0xd2, 0xe9, 0x7a, 0xa7, 0xff, 0x3c, 0xbb, 0xfc, 0x49, 0xed, 0xdb, 0xff, 0x18, 0x1f, 0xf7, 0xe5, 0xed, 0x7a, 0xd2, 0xfa, 0xf0, 0xe8, 0x23, 0xbf, 0x8d, 0xf7, 0xe9, 0x78, 0x21, 0xef, 0x83, 0xc1, 0xc0, 0x43, 0xa5, 0xa5, 0xbf, 0xc1, 0xc0, 0x44, 0x9e, 0xf8, 0xf8, 0x21, 0xa1, 0xfc, 0xb8, 0x74, 0x61, 0xea, 0x97, 0xfc, 0x78, 0xd3, 0xd5, 0x7f, 0xe6, 0x04, 0xa6, 0xf7, 0xc8, 0x20, 0x96, 0xbf, 0x1b, 0xe1, 0x40, 0x43, 0xdf, 0x17, 0x94, 0xf5, 0x9a, 0xbf, 0xac, 0x43, 0xee, 0xfc, 0x4d, 0x60, 0xc3, 0xc1, 0x0f, 0x60, 0xd5, 0x7b, 0xb0, 0xe7, 0x07, 0xf5, 0xd7, 0x10, 0x0c, 0x38, 0x81, 0x27, 0xab, 0xbf, 0xfb, 0x22, 0xd7, 0x01, 0x03, 0x3e, 0xa9, 0x7f, 0xe0, 0x41, 0xac, 0x6f, 0x8c, 0xeb, 0xf0, 0x49, 0xef, 0x97, 0x7e, }, { // nolint: dupl 0x80, 0x60, 0x75, 0x2f, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x01, 0x51, 0x27, 0xaf, 0x5f, 0xe2, 0x0f, 0x5d, 0x97, 0xfd, 0x4c, 0x93, 0x61, 0x8f, 0x0c, 0x9e, 0xbb, 0xfc, 0x99, 0x01, 0x3f, 0x40, 0x5a, 0xb1, 0xfb, 0x8a, 0x0c, 0xba, 0xfc, 0x78, 0x20, 0x2f, 0x6d, 0x70, 0x76, 0x37, 0xaf, 0x90, 0x5e, 0xbf, 0x7e, 0x11, 0x15, 0xeb, 0xb5, 0x58, 0x13, 0x41, 0x16, 0xbb, 0x1e, 0x50, 0xb7, 0xa2, 0x6b, 0xa5, 0x7e, 0x9f, 0xc1, 0x29, 0x44, 0xf5, 0xeb, 0xc0, 0x82, 0x55, 0xf9, 0xc0, 0xca, 0x04, 0x00, 0xe7, 0xb6, 0xbf, 0xb7, 0xe0, 0x40, 0x15, 0xdd, 0xf5, 0xe0, 0x69, 0x2b, 0xb5, 0xfc, 0xba, 0x7e, 0xaa, 0xd8, 0x47, 0x0e, 0xb1, 0x8b, 0xf6, 0x7f, 0x05, 0x81, 0xbd, 0x7e, 0xd7, 0xa7, 0xea, 0x5b, 0x2f, 0xb0, 0xe9, 0xb4, 0xbc, 0x14, 0xde, 0xfc, 0x33, 0xe8, 0xff, 0xb7, 0xfb, 0x39, 0x41, 0x8a, 0xb7, 0x18, 0x43, 0xf5, 0xb7, 0xff, 0x20, 0x6b, 0xdd, 0x6f, 0xfe, 0x71, 0x85, 0xda, 0xf8, 0x11, 0x47, 0x77, 0xf7, 0xaf, 0xa2, 0x08, 0xf7, 0xa0, 0xbd, 0x14, 0x11, 0xd1, 0xfe, 0xe0, 0x92, 0x08, 0xbb, 0x58, 0xb8, 0x78, 0x14, 0x1b, 0xd7, 0x94, 0x5f, 0x5f, 0x6f, 0x0d, 0x06, 0x17, 0xbc, 0x9e, 0x0b, 0x03, 0x3e, 0x9a, 0xd2, 0xff, 0x02, 0xa0, 0x25, 0x05, 0x5d, 0x1f, 0x3e, 0x6d, 0x71, 0x70, 0xdc, 0x27, 0xb7, 0x65, 0xaf, 0x0f, 0x84, 0x15, 0xfc, 0x80, 0x86, 0xfe, 0x2c, 0x3b, 0xb5, 0xff, 0xdb, 0xf2, 0x27, 0xda, 0xf8, 0x62, 0x5f, 0x4b, 0x16, 0x09, 0x1f, 0xb7, 0x84, 0x40, 0xb2, 0x2f, 0xdd, 0xff, 0x04, 0x01, 0x9a, 0x2e, 0xb9, 0x77, 0xfc, 0x3a, 0x08, 0xec, 0x92, 0xfb, 0xc2, 0x1d, 0x97, 0xca, 0x7a, 0xa7, 0xff, 0xc1, 0x2e, 0x60, 0x81, 0x7d, 0xaf, 0x15, 0x73, 0xd6, 0xbf, 0xb8, 0x1c, 0x42, 0x3c, 0x08, 0x03, 0x17, 0xb8, 0x91, 0xa7, 0xae, 0x9f, 0xf3, 0x82, 0x0e, 0x20, 0xa4, 0xf7, 0x58, 0x54, 0xfe, 0xf4, 0xff, 0x84, 0x3c, 0xb5, 0x61, 0x55, 0xef, 0x9e, 0xa9, 0xff, 0xd7, 0x81, 0x67, 0xe2, 0x7c, 0x4a, 0xd7, 0x97, 0xea, 0xff, 0x58, 0x2a, 0x8b, 0xd0, 0x95, 0xd7, 0x83, 0x03, 0xd5, 0xbf, 0xfc, 0x30, 0x6b, 0x17, 0x79, 0x3c, 0xa4, 0xf5, 0xc7, 0x78, 0x28, 0xab, 0x77, 0xef, 0xf5, 0x73, 0x81, 0x12, 0x19, 0xaf, 0x5f, 0xdb, 0xf2, 0xfc, 0xda, 0x75, 0xc1, 0x20, 0xd0, 0xc5, 0x2f, 0xdc, 0xc9, 0xfe, 0x9e, 0x04, 0x00, 0x6a, 0x1c, 0xe5, 0x45, 0x7f, 0xa6, 0x97, 0xc0, 0x82, 0x08, 0xbd, 0x71, 0xfd, 0xfa, 0xec, 0x86, 0xe4, 0xcf, 0x27, 0xc3, 0x17, 0xf7, 0x6a, 0xb5, 0xf6, 0xeb, 0x1a, 0x0a, 0x2b, 0xd3, 0xf1, 0x78, 0x80, 0xdf, 0xa6, 0xbe, 0xdf, 0xc1, 0x78, 0x25, 0xe0, 0x2a, 0x21, 0x2a, 0xfb, 0xf8, 0xa2, 0x13, 0x4b, 0xdd, 0xf9, 0x98, 0xc3, 0xe0, 0xc0, 0xf5, 0xef, 0xfc, 0xa7, 0xb1, 0xfe, 0x9e, 0x61, 0xa4, 0xf6, 0xb1, 0x5e, 0x52, 0x8c, 0xeb, 0xd7, 0x4b, 0x4f, 0xe5, 0x12, 0xad, 0xc3, 0x52, 0x76, 0xf8, 0x25, 0x12, 0x0c, 0x37, 0xbf, 0x76, 0xbf, 0xfe, 0x40, 0x9f, 0xb4, 0xd7, 0xe0, 0xb7, 0xca, 0x7a, 0xf5, 0xf8, 0x67, 0x40, 0x5b, 0xfd, 0x3b, 0x7f, 0xcb, 0x87, 0xc3, 0x01, 0x9f, 0x76, 0x0f, 0xfb, 0x7c, 0x30, 0x08, 0xfd, 0x28, 0x38, 0x24, 0x1a, 0x16, 0xf6, 0xd1, 0x75, 0xd3, 0xfe, 0x1e, 0x28, 0x8b, 0xbe, 0xff, 0x05, 0x23, 0x0f, 0xad, 0xaf, 0xf1, 0x7e, 0x0a, 0x06, 0x9b, 0x7f, 0x17, 0xf0, 0x8d, 0x65, 0x56, 0xac, 0x8b, 0xaf, 0x04, 0xab, 0x95, 0xe1, 0x53, 0x5f, 0xf0, 0xaf, 0x08, 0x84, 0x0f, 0x54, 0xff, 0xe6, 0xf1, 0x03, 0x3e, 0x08, 0x7d, 0xe0, 0xe2, 0x49, 0xc4, 0x91, 0x60, 0xf2, 0x9e, 0xac, 0xbf, 0xf9, 0xeb, 0xd9, 0x7f, 0x27, 0x03, 0x14, 0xfe, 0xae, 0x9f, 0xea, 0x7c, 0xbd, 0x3f, 0xc2, 0xc5, 0x5d, 0x76, 0x45, 0xcb, 0x83, 0xe0, 0x20, 0x78, 0x83, 0xd5, 0x3f, 0xfc, 0x87, 0xad, 0x3f, 0xd6, 0x19, 0x5e, 0xf2, 0x9e, 0xa9, 0x7f, 0xd1, 0x0f, 0x54, 0xff, 0xf2, 0x1e, 0xbb, 0xff, 0x75, 0x95, 0x77, 0xe8, 0x09, 0xe1, 0x10, 0xb7, 0xbd, 0xee, 0xbd, 0xff, 0x83, 0xd0, 0xd5, 0xf7, 0x5f, 0x5f, 0x8e, 0xf0, 0xec, 0x9d, 0xfa, 0x9e, 0xbe, 0xbf, 0x0f, 0x78, 0x7e, 0x5e, 0xaf, 0x82, 0x6a, 0xd7, 0x03, 0xf0, 0x81, 0xbd, 0xbd, 0xdd, 0x29, 0x7f, 0xa3, 0xd7, 0xed, 0xf8, 0x08, 0xa1, 0x00, 0x83, 0xd3, 0x8c, 0x78, 0xc0, 0xbe, 0x04, 0x42, 0x5a, 0xff, 0x4f, 0x02, 0x40, 0x40, 0x14, 0xf5, 0x61, 0x45, 0x74, 0x36, 0x1a, 0xf7, 0xe0, 0x69, 0x8f, 0xef, 0xb2, 0xa4, 0x80, 0xfe, 0x8a, 0x37, 0x4b, 0x7b, 0x06, 0x35, 0xe0, 0xf6, 0xa5, 0xf4, 0xa2, 0xd3, 0xf6, 0x17, 0xb7, 0xa8, 0x20, 0xed, 0xd8, 0x24, 0x16, 0x06, 0xe8, 0xf9, 0x7d, 0x7f, 0xa7, 0xca, 0x2e, 0x81, 0x20, 0x16, 0x5a, 0xa0, 0x5e, 0x08, 0xa0, 0xaf, 0x4b, 0x46, 0xc2, 0xe2, 0xc1, 0x6f, 0xf0, 0x8f, 0x7c, 0x80, 0x80, 0xfb, 0x07, 0xe0, 0x5b, 0x9a, 0x92, 0x07, 0xf2, 0x06, 0x32, 0x11, 0xe4, 0x04, 0x1d, 0x53, 0xff, 0x8a, 0x10, 0x1f, 0xed, 0x50, 0xc2, 0xde, 0x3e, 0x25, 0xc6, 0x5a, 0x20, 0x3e, 0x55, 0x76, 0xef, 0xa7, 0x89, 0x82, 0xbe, 0xf1, 0x92, 0xed, 0x80, 0xbe, 0x50, 0x40, 0x12, 0x90, 0x72, 0xa7, 0x06, 0x10, 0x41, 0xd1, 0x74, 0x76, 0x6b, 0xaa, 0x7f, 0xf0, 0x53, 0x36, 0xc1, 0xf8, 0x11, 0x61, 0x8a, 0x74, 0x72, 0xe3, 0x0b, 0xeb, 0x7f, 0xf5, 0x19, 0xeb, 0xbc, 0xc0, 0xc5, 0x96, 0x12, 0xe1, 0x68, 0xf0, 0xa1, 0x02, 0x37, 0xe6, 0x24, 0xc1, 0xf2, 0xde, 0xf8, 0x20, 0x08, 0x0b, 0xbe, 0x35, 0xad, 0x00, 0xab, 0xe0, 0x27, 0x46, 0x02, 0x8e, 0xc2, 0xa4, 0xdf, 0xdc, 0x0f, 0xa3, 0x05, 0xe8, 0x1e, 0x8f, 0xe0, 0xe2, 0x09, 0x25, 0x1e, 0xe9, 0x56, 0x0b, 0x21, 0x8b, 0x06, 0x62, 0x57, 0x65, 0xd8, 0x2f, 0xfc, 0x74, 0x10, 0x79, 0x61, 0xd9, 0xd1, 0xa0, 0x75, 0xfd, 0x3e, 0x09, 0xe3, 0x67, 0xcf, 0xef, 0xd0, 0x09, 0x05, 0x94, 0x12, 0xa8, 0xff, 0xe0, 0xc0, 0x40, 0x20, 0xbe, 0xfb, 0xf6, 0x0d, 0x13, 0xef, 0xb3, 0xf8, 0x7e, 0x18, 0xe9, 0x0f, 0x8f, 0xed, 0x3a, 0x7a, 0xbe, 0xa2, 0xd8, 0x5d, 0x3c, 0x0a, 0x70, 0x51, 0xce, 0x20, 0x23, 0xe4, 0x5f, 0x28, 0xc9, 0xd3, 0x81, 0x24, 0x3a, 0x18, 0xda, 0x31, 0x05, 0xd4, 0xe3, 0x41, 0x46, 0x5e, 0xdf, 0xe0, 0x62, 0x28, 0x63, 0xa8, 0x6f, 0xf2, 0x12, 0xad, 0xaf, 0xf0, 0x4a, 0x50, 0xe7, 0x40, 0x92, 0xab, 0x5f, 0xf1, 0xd0, 0xdf, 0x0f, 0x27, 0x8a, 0xd0, 0x4a, 0xbf, 0xef, 0x81, 0x00, 0x68, 0xbe, 0x8b, 0x98, 0x68, 0xf5, 0xc1, 0xfc, 0x5f, 0x32, 0x23, 0xd3, 0x4b, 0xe1, 0x0e, 0xfe, 0x38, 0x42, 0xda, 0x3e, 0xc1, 0xe0, 0x65, 0x82, 0x4d, 0xaa, 0x5d, 0xc0, 0x86, 0x10, 0x05, 0x1c, 0xc7, 0x0a, 0x3b, 0x9c, 0x61, 0x41, 0x77, 0x02, 0xe4, 0x39, 0xe8, 0x17, 0x5f, 0xff, 0x84, 0x7d, 0xf4, 0x02, 0x60, 0xf4, 0x98, 0x3c, 0x1a, 0x4b, 0xca, 0x31, 0xf9, 0xbd, 0x00, 0xb0, 0x4b, 0x17, 0xe4, 0x19, 0xd8, 0x36, 0x7e, 0x18, 0xf6, 0x17, 0x40, 0xdb, 0xa3, 0xfe, 0x0f, 0x21, 0xce, 0x86, 0x9a, 0xdb, 0xff, 0x85, 0x05, 0xf6, 0x67, 0x31, 0xd8, 0x1b, 0x05, 0x85, 0x6b, 0xdc, 0x18, 0x14, 0xdc, 0xa3, 0x0f, 0x08, 0x94, 0x14, 0x76, 0x0f, 0xb5, 0x31, 0x70, 0x3c, 0x04, 0x43, 0x1e, 0x43, 0x5a, 0x07, 0xea, 0x69, 0xdb, 0xfc, 0xf0, 0x49, 0x5a, 0x06, 0xc1, 0x77, 0x06, 0x91, 0x7d, 0x03, 0x40, 0xf6, 0x0d, 0xbd, 0x7c, 0x82, 0xfa, 0x04, 0x72, 0xfa, 0x01, 0x32, 0xc1, 0xe4, 0x5f, 0xa3, 0xed, 0x60, 0xfe, 0x2f, 0xa0, 0x5d, 0x03, 0x67, 0x83, 0xc8, 0xbb, 0xe9, 0x69, 0x70, 0xcc, 0x5f, 0x29, 0xc2, 0x11, 0xcc, 0x21, 0x40, 0xfc, 0xdd, 0x9f, 0x45, 0x37, 0x53, 0x23, 0xc0, 0x40, 0xc3, 0x9c, 0xde, 0xa9, 0xff, 0xc0, 0x49, 0x94, 0x31, 0xcd, 0x46, 0x7b, 0xf0, 0xf5, 0xfe, 0x24, 0x37, 0xd6, 0xbb, 0x7f, 0xe2, 0x17, 0xbc, 0x86, 0xf6, 0xdd, 0x15, 0x7b, 0xa2, 0x9e, 0x29, 0xbd, 0xbf, 0xca, 0x18, 0xf2, 0xaf, 0x56, 0x10, 0xf0, 0x80, 0x43, 0xd8, 0xbb, 0x16, 0xc6, 0xc1, 0x63, 0xa1, 0xab, 0x57, 0xad, 0x2d, 0xbf, 0x87, 0x7c, 0x4c, 0x13, 0xfb, 0x0b, 0x46, 0x96, 0x5c, 0x17, 0x45, 0xf5, 0xe8, 0x1f, 0x04, 0x30, 0x43, 0x5e, 0xc3, 0x01, 0x21, 0x0c, 0x5e, 0x03, 0xb7, 0x4b, 0x0b, 0x34, 0xb6, 0x97, 0x2c, 0xce, 0xbf, 0x6f, 0xc0, 0x97, 0x0d, 0xf2, 0xd9, 0x6e, 0x2f, 0x41, 0x69, 0x76, 0x50, 0xa6, 0x86, 0x92, 0x69, 0xc7, 0x6c, 0xa9, 0x18, 0x9a, 0x35, 0xea, 0x11, 0xed, 0xb8, 0xb9, 0x21, 0x3f, 0x86, 0x50, 0xcf, 0xf6, 0x7c, 0x08, 0xf1, 0xbc, 0x79, 0xe9, 0x4f, 0x31, 0xc2, 0x98, 0xee, 0xcf, 0x5e, 0xcf, 0x7a, 0x5f, 0xf0, 0x75, 0x04, 0x1e, 0x80, 0xd2, 0xf2, 0xfd, 0x5b, 0xff, 0xb8, 0xce, 0x51, 0x76, 0xbb, 0x03, 0xeb, 0x1f, 0xd7, 0xf8, 0x20, 0x84, 0x3b, 0x9f, 0xdf, 0x9a, 0x9e, 0x08, 0xe1, 0x3e, 0xd2, 0x34, 0x1d, 0x78, 0x25, 0x8c, 0xe3, 0x85, 0x86, 0xae, 0x82, 0x7e, 0x5e, 0xdb, 0x0b, 0xc1, 0x24, 0x23, 0xc3, 0x73, 0xfd, 0xb8, 0x92, 0x36, 0xbb, 0xfc, 0x1e, 0x42, 0x9d, 0xfb, 0x05, 0xb0, 0x79, 0x88, 0x90, 0xd3, 0xec, 0x71, 0x51, 0x74, 0xb9, 0xf4, 0xd5, 0xf1, 0x71, 0x74, 0x7d, 0xfe, 0x3a, 0x2f, 0x60, 0xba, 0x5e, 0x22, 0x18, 0xa4, 0xc2, 0xc8, 0x48, 0x20, 0xe0, 0xff, 0x6e, 0xbf, 0xb7, 0xc1, 0x44, 0x14, 0x56, 0x36, 0xfc, 0x1f, 0x10, 0x7b, 0xf0, 0xbc, 0x5c, 0x25, 0xc2, 0xfa, 0x78, 0xff, 0x60, 0xbe, 0x12, 0x9a, 0xc1, 0x87, 0x17, 0xa1, 0xf8, 0x11, 0x22, 0xed, 0x10, 0x11, 0xb1, 0x8f, 0xb0, 0x87, 0xe1, 0xb8, 0x6e, 0x61, 0x62, 0x12, 0x7d, 0x7f, 0x4f, 0x80, 0x86, 0x8b, 0x98, 0x18, 0x8b, 0x1c, 0x2f, 0xd4, 0x37, 0xfe, 0x36, 0x2f, 0x94, 0x5b, 0x60, 0x98, 0xfc, 0x1c, 0xc5, 0xc7, 0x47, 0xcb, 0xc1, 0x55, 0xec, 0x1b, 0x5e, 0x02, 0xb2, 0x6d, 0x1f, 0x86, 0x66, 0x9c, 0x97, 0xc5, 0x45, 0xc8, 0x3c, 0x3c, 0xfb, 0xe6, 0xfa, 0x61, 0x14, 0x17, 0xc7, 0x90, 0x31, 0x1e, 0xf2, 0xf7, 0xdf, 0xc9, 0x88, 0x3e, 0x63, 0xda, 0xdb, 0xe0, 0x9c, 0x81, 0x88, }, { // nolint: dupl 0x80, 0xe0, 0x75, 0x30, 0x1c, 0x45, 0xb0, 0xac, 0x4b, 0x39, 0x6e, 0x7a, 0x7c, 0x41, 0xf8, 0x61, 0x5c, 0x18, 0x0f, 0xbb, 0x15, 0xe9, 0x56, 0xd7, 0xf0, 0x7d, 0x17, 0xb0, 0xaa, 0x1b, 0xc9, 0xfc, 0x08, 0x91, 0x73, 0x17, 0x67, 0xa0, 0x43, 0x45, 0xcf, 0xc0, 0x89, 0x17, 0x46, 0x86, 0xe0, 0xb4, 0x63, 0x7a, 0x5f, 0x05, 0x31, 0x7b, 0x5b, 0x06, 0xc2, 0x48, 0xd2, 0xc2, 0x30, 0x51, 0x76, 0x0f, 0xb5, 0xd9, 0xc1, 0x84, 0x5c, 0x27, 0xc3, 0x3b, 0xff, 0x29, 0x07, 0xc1, 0x6c, 0x5d, 0x19, 0x89, 0xf1, 0xa0, 0xde, 0xc0, 0xef, 0x01, 0x41, 0x17, 0x71, 0x22, 0x88, 0x7a, 0x03, 0x0e, 0xa7, 0xa3, 0xd7, 0x88, 0x8b, 0xc7, 0x61, 0x47, 0x5c, 0x67, 0xe0, 0xea, 0x59, 0x07, 0xed, 0x73, 0x90, 0x10, 0x77, 0x86, 0x33, 0x42, 0xd6, 0xbe, 0xab, 0xc3, 0xea, 0x7c, 0x95, 0x7f, 0x4b, 0x16, 0x40, 0xc4, 0x1b, 0x9a, 0x0a, 0xf6, 0xbe, 0x3f, 0xb1, 0xaf, 0x65, 0x2a, 0xfd, 0x2e, 0x06, 0x88, 0xbd, 0x04, 0x5a, 0x30, 0xe1, 0x94, 0x1b, 0xc7, 0xe3, 0x7c, 0x23, 0x47, 0xd0, 0x36, 0x50, 0xf5, 0x7e, 0x0f, 0x25, 0xd2, 0x84, 0x8d, 0x75, 0x70, 0x4d, 0x0f, 0xe4, 0x1f, 0xd8, 0x52, 0x20, 0xa0, 0x61, 0x0b, 0xcf, 0x5c, 0x6b, 0xd2, 0xfc, 0x10, 0x4d, 0xe3, 0xfa, 0xe1, 0xa8, 0xbd, 0x27, 0xcf, 0xd0, 0x1f, 0x07, 0xb3, 0x4b, 0xe7, 0x8f, 0x18, 0x50, 0xff, 0x57, 0x31, 0xc6, 0x92, 0xc7, 0x7c, 0xb4, 0x61, 0x45, 0x71, 0xcf, 0xf6, 0xf1, 0xa5, 0x0f, 0xd1, 0xb7, 0x92, 0xaf, 0xb6, 0x92, 0x6c, 0xf6, 0x6c, 0xc2, 0x07, 0x1b, 0xcd, 0xf4, 0x88, 0x8c, 0xba, 0x0b, 0xf0, 0x25, 0x41, 0x05, 0xf5, 0xb3, 0x24, 0x6c, 0x83, 0x07, 0xc8, 0x3c, 0xe7, 0xe7, 0xfe, 0x0e, 0x61, 0x7a, 0x2e, 0x69, 0xf5, 0xf7, 0xf8, 0xb8, 0x20, 0xb7, 0xc8, 0x41, 0x19, 0x7d, 0xfc, 0xa4, 0x41, 0x4c, 0x7f, 0xbd, 0x7f, 0xb5, 0x81, 0x2e, 0x18, 0xe8, 0xad, 0x4f, 0x75, 0xf4, 0xe6, 0xce, 0x78, 0xcd, 0xbb, 0xa1, 0xdd, 0xdc, 0x75, 0xb6, 0xfb, 0x8b, 0xf0, 0x84, 0x31, 0x7a, 0x08, 0x62, 0x35, 0xbf, 0x5f, 0xa7, 0xe0, 0x68, 0x86, 0x34, 0x12, 0x0a, 0x32, 0xd9, 0x01, 0x6e, 0xbf, 0x6f, 0xc3, 0xf0, 0x57, 0x5d, 0x43, 0x99, 0x46, 0x0f, 0x7d, 0x8f, 0x25, 0xd0, 0x64, 0x36, 0x98, 0xda, 0xfe, 0xc3, 0x02, 0x94, 0x10, 0x57, 0xa3, 0x8b, 0x3e, 0x7c, 0xa2, 0xd7, 0xfd, 0x3c, 0x16, 0x41, 0x45, 0xf9, 0x07, 0xef, 0x63, 0x96, 0x36, 0xc6, 0x95, 0xf3, 0xf9, 0x33, 0x4a, 0x82, 0x40, 0xcf, 0x7a, 0x3d, 0xf5, 0xf8, 0xc8, 0x43, 0xde, 0x81, 0xfa, 0xb2, 0xc6, 0x90, 0x13, 0x56, 0xb6, 0x07, 0xdb, 0x80, 0x8b, 0x29, 0xe7, 0xbf, 0xaf, 0x03, 0x81, 0x3c, 0xa4, 0xe9, 0xfd, 0x7b, 0xcb, 0xd8, 0x53, 0xc2, 0x35, 0x81, 0x06, 0x4c, 0xbc, 0x35, 0xe1, 0xff, 0x59, 0x20, 0x7b, 0xf7, 0xe0, 0x46, 0xf0, 0x7b, 0x77, 0x60, 0xfc, 0x6d, 0xec, 0x1f, 0x84, 0x24, 0xd0, 0x3f, 0x04, 0x5e, 0x04, 0x18, 0x88, 0xe9, 0x70, 0xc0, 0x7d, 0xfe, 0x26, 0x49, 0x68, 0x81, 0xf8, 0x39, 0x88, 0xa0, 0x4c, 0x1e, 0x42, 0x5f, 0x0a, 0xc4, 0x6c, 0x1f, 0x4b, 0xc2, 0x12, 0x50, 0x3f, 0xe4, 0x99, 0x84, 0x0f, 0xc7, 0xc4, 0x50, 0x5d, 0x9f, 0xc0, 0x81, 0x3d, 0x68, 0x2f, 0xf0, 0x51, 0xe0, 0x49, 0xba, 0x45, 0x25, 0xe0, 0xa7, 0xc1, 0x75, 0xe6, 0x25, 0x60, 0xf0, 0xe4, 0x55, 0x1a, 0x2f, 0xa5, 0x92, 0x19, 0xf6, 0x0d, 0x6c, 0x2f, 0xf0, 0x5b, 0x17, 0xd2, 0x4b, 0x49, 0x2c, 0x31, 0x2f, 0xb3, 0xc7, 0x78, 0xb8, 0x4b, 0xdf, 0xbe, 0x1e, 0x28, 0x8e, 0xfe, 0xf8, 0x34, 0xf0, 0x43, 0x3d, 0x7d, 0xbf, 0x80, 0x83, 0xf0, 0xb5, 0xf7, 0xe0, 0x50, 0x88, 0xec, 0x17, 0x4f, 0x8a, 0xf0, 0xc7, 0x81, 0x26, 0xfd, 0x03, 0xc4, 0x78, 0x37, 0x86, 0x7d, 0x83, 0x5a, 0x7f, 0xe0, 0x51, 0xf0, 0x37, 0x94, 0x11, 0x79, 0x81, 0x7d, 0xc1, 0xad, 0xf4, 0x0d, 0x03, 0xf1, 0x1e, 0x81, 0xf6, 0x0b, 0x83, 0xe8, 0x21, 0xe8, 0xfd, 0xc1, 0xff, 0x82, 0xbf, 0x03, 0x9d, 0xf2, 0x12, 0xe0, 0x8a, 0x23, 0x7b, 0x07, 0x94, 0x10, 0x26, 0xb5, 0x1a, 0x42, 0x76, 0x0f, 0x87, 0xa2, 0x3d, 0x03, 0xf6, 0x0f, 0x02, 0x1f, 0x9a, 0x4e, 0xc1, 0x20, 0x79, 0x3c, 0x08, 0x7e, 0x19, 0xf0, 0x8c, 0x35, 0xc5, 0x83, 0xad, 0xaf, 0xf5, 0x7e, 0x80, 0x58, 0x4a, 0x4e, 0x81, 0xf0, 0x5f, 0x27, 0x94, 0x7f, 0x3c, 0x33, 0xea, 0xac, 0x2f, 0xf8, 0x3c, 0x88, 0xf4, 0x0f, 0x40, 0xb8, 0x09, 0x08, 0x67, 0xd9, 0x91, 0x34, 0xff, 0xc0, 0xc1, 0xe0, 0x55, 0xbe, 0x81, 0xf3, 0xc4, 0x74, 0x7d, 0x1a, 0x07, 0x87, 0xbf, 0x27, 0x20, 0xb7, 0x07, 0x32, 0x76, 0x0d, 0xbc, 0x1e, 0x49, 0xd1, 0xf5, 0x11, 0xd0, 0x2f, 0x40, 0x97, 0x25, 0x0f, 0x96, 0x37, 0x57, 0xef, 0xc1, 0xa7, 0x83, 0x2f, 0x01, 0x11, 0xe0, 0xd3, 0xc0, 0x8f, 0xeb, 0xde, 0xaf, 0xcd, 0xe2, 0x32, 0x47, 0x6a, 0x09, 0x75, 0x07, 0x9e, 0x6f, 0x04, 0x7f, 0x27, 0x2f, 0xc9, 0xc3, 0x3f, 0x27, 0x15, 0xa8, 0x57, 0xc3, 0xfe, 0x06, 0x2f, 0x37, 0x81, 0x17, 0xc1, 0xef, 0x82, 0x2f, 0x05, 0x1e, 0x27, 0xc1, 0xa7, 0x82, 0x3f, 0x03, 0x97, 0x88, 0xf1, 0xbb, 0x81, 0xab, 0xc0, 0xb9, 0xe0, 0x5f, 0xf0, 0x99, 0x49, 0x66, 0x52, 0x3e, 0x02, 0x73, 0xc1, 0x57, 0x86, 0xbc, 0x25, 0x11, 0x7e, 0xd7, 0xcb, 0xe1, 0x6f, 0x5e, 0x05, 0xaf, 0x0a, 0xea, 0x0b, 0xbf, 0xe1, 0x8f, 0x04, 0x3e, 0x06, 0x1f, 0x1b, 0xe6, 0xdc, 0x0c, 0x5e, 0x0f, 0x71, 0x34, 0x37, 0xac, 0x90, 0x3d, 0xf9, 0xfc, 0x08, 0xc1, 0xde, 0x04, 0x6f, 0x5e, 0xbd, 0x6a, 0x27, 0xc4, 0x88, 0xec, 0xb5, 0x1f, 0xa8, 0x24, 0xd4, 0x1e, 0xf8, 0x21, 0xf0, 0x45, 0xe0, 0xc3, 0xc4, 0xf8, 0xcf, 0x0c, 0x78, 0xef, 0x17, 0xff, 0x0a, 0x6a, 0x06, 0x3f, 0x04, 0xa4, 0xe0, 0x44, 0xf0, 0x41, 0xe0, 0xc7, 0x51, 0x1e, 0x23, 0xc0, 0xa3, 0xe0, 0x51, 0xf2, 0x6b, 0xad, 0x79, 0x38, 0x17, 0x72, 0x40, 0x93, 0xe0, 0x97, 0xc2, 0xba, 0x93, 0xd7, 0x97, 0xc1, 0x4f, 0x85, 0x3c, 0x6f, 0x82, 0xff, 0x05, 0xfc, 0x4e, 0xb8, 0x36, 0xf0, 0x30, 0x6a, 0x7f, 0x3e, 0xe0, 0xf3, 0xc1, 0xe6, 0x78, 0xdf, 0x1b, 0xef, 0xdf, 0x83, 0x4f, 0x06, 0x5e, 0x06, 0xcf, 0x06, 0x9e, 0x04, 0x7d, 0x5e, 0xaf, 0xfe, 0x23, 0x59, 0x6a, 0x3b, 0x50, 0x4b, 0xa8, 0x3c, 0xf8, 0xce, 0x08, 0xf5, 0x2e, 0xa1, 0x7f, 0x7e, 0x2b, 0x50, 0xae, 0xa0, 0x62, 0xd4, 0x08, 0xbe, 0xfc, 0x18, 0x78, 0x28, 0xf1, 0x3a, 0x81, 0x43, 0x52, 0xf9, 0x77, 0x02, 0xe7, 0x81, 0x73, 0xc1, 0x1f, 0x84, 0xfc, 0x09, 0x1e, 0x0f, 0xbc, 0x29, 0xe1, 0x2f, 0x2e, 0xa4, 0xd4, 0x4e, 0xa1, 0x0f, 0x02, 0x97, 0x82, 0xfd, 0x40, 0xc3, 0xe0, 0x61, 0xf3, 0x66, 0x97, 0x11, 0x44, 0x36, 0x09, 0x7e, 0x07, 0xfb, 0x2e, 0xe0, 0xd4, 0xbc, 0x0d, 0xba, 0x81, 0x1b, 0xc0, 0x8d, 0xeb, 0xd7, 0xad, 0x44, 0xf8, 0x9d, 0xc7, 0xea, 0x09, 0x35, 0x07, 0xbe, 0x08, 0x7c, 0x11, 0x78, 0x22, 0xf2, 0x79, 0x3c, 0x2d, 0xe1, 0x0f, 0x17, 0xff, 0x0a, 0x78, 0x7b, 0xc0, 0xc7, 0xe0, 0x97, 0xc0, 0x89, 0xe1, 0x2f, 0x06, 0x3a, 0x88, 0xf1, 0x1e, 0x05, 0x1f, 0x02, 0x6f, 0x93, 0xc0, 0xc7, 0x9e, 0x04, 0x9f, 0x0f, 0xf8, 0x57, 0x52, 0x6a, 0x5f, 0x2f, 0x88, 0xf1, 0x1e, 0x08, 0xf5, 0x05, 0xde, 0x0b, 0xbc, 0x0c, 0x1a, 0x9f, 0xcf, 0xf2, 0xd4, 0x1e, 0x78, 0x3c, 0xcb, }, } } rtp-1.8.27/codecs/h264_packet.go000066400000000000000000000202771512154406600162330ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "bytes" "encoding/binary" "fmt" ) // H264Payloader payloads H264 packets. type H264Payloader struct { spsNalu, ppsNalu []byte DisableStapA bool } const ( stapaNALUType = 24 fuaNALUType = 28 fubNALUType = 29 spsNALUType = 7 ppsNALUType = 8 audNALUType = 9 fillerNALUType = 12 fuaHeaderSize = 2 stapaHeaderSize = 1 stapaNALULengthSize = 2 naluTypeBitmask = 0x1F naluRefIdcBitmask = 0x60 fuStartBitmask = 0x80 fuEndBitmask = 0x40 outputStapAHeader = 0x78 ) // nolint:gochecknoglobals var ( naluStartCode = []byte{0x00, 0x00, 0x01} annexbNALUStartCode = []byte{0x00, 0x00, 0x00, 0x01} ) func emitNalus(nals []byte, emit func([]byte)) { // look for 3-byte NALU start code start := bytes.Index(nals, naluStartCode) offset := 3 if start == -1 { // no start code, emit the whole buffer emit(nals) return } length := len(nals) for start < length { // look for the next NALU start (end of this NALU) end := bytes.Index(nals[start+offset:], naluStartCode) if end == -1 { // no more NALUs, emit the rest of the buffer emit(nals[start+offset:]) break } // next NALU start nextStart := start + offset + end // check if the next NALU is actually a 4-byte start code endIs4Byte := nals[nextStart-1] == 0 if endIs4Byte { nextStart-- } emit(nals[start+offset : nextStart]) start = nextStart if endIs4Byte { offset = 4 } else { offset = 3 } } } // Payload fragments a H264 packet across one or more byte arrays. func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint:cyclop var payloads [][]byte if len(payload) == 0 { return payloads } emitNalus(payload, func(nalu []byte) { if len(nalu) == 0 { return } naluType := nalu[0] & naluTypeBitmask naluRefIdc := nalu[0] & naluRefIdcBitmask switch { case naluType == audNALUType || naluType == fillerNALUType: return case naluType == spsNALUType: if !p.DisableStapA { p.spsNalu = nalu return } case naluType == ppsNALUType: if !p.DisableStapA { p.ppsNalu = nalu return } case !p.DisableStapA && p.spsNalu != nil && p.ppsNalu != nil: // Pack current NALU with SPS and PPS as STAP-A spsLen := make([]byte, 2) binary.BigEndian.PutUint16(spsLen, uint16(len(p.spsNalu))) // nolint: gosec // G115 ppsLen := make([]byte, 2) binary.BigEndian.PutUint16(ppsLen, uint16(len(p.ppsNalu))) // nolint: gosec // G115 stapANalu := []byte{outputStapAHeader} stapANalu = append(stapANalu, spsLen...) stapANalu = append(stapANalu, p.spsNalu...) stapANalu = append(stapANalu, ppsLen...) stapANalu = append(stapANalu, p.ppsNalu...) if len(stapANalu) <= int(mtu) { out := make([]byte, len(stapANalu)) copy(out, stapANalu) payloads = append(payloads, out) } p.spsNalu = nil p.ppsNalu = nil } // Single NALU if len(nalu) <= int(mtu) { out := make([]byte, len(nalu)) copy(out, nalu) payloads = append(payloads, out) return } // FU-A maxFragmentSize := int(mtu) - fuaHeaderSize // The FU payload consists of fragments of the payload of the fragmented // NAL unit so that if the fragmentation unit payloads of consecutive // FUs are sequentially concatenated, the payload of the fragmented NAL // unit can be reconstructed. The NAL unit type octet of the fragmented // NAL unit is not included as such in the fragmentation unit payload, // but rather the information of the NAL unit type octet of the // fragmented NAL unit is conveyed in the F and NRI fields of the FU // indicator octet of the fragmentation unit and in the type field of // the FU header. An FU payload MAY have any number of octets and MAY // be empty. // According to the RFC, the first octet is skipped due to redundant information naluIndex := 1 naluLength := len(nalu) - naluIndex naluRemaining := naluLength if minInt(maxFragmentSize, naluRemaining) <= 0 { return } for naluRemaining > 0 { currentFragmentSize := minInt(maxFragmentSize, naluRemaining) out := make([]byte, fuaHeaderSize+currentFragmentSize) // +---------------+ // |0|1|2|3|4|5|6|7| // +-+-+-+-+-+-+-+-+ // |F|NRI| Type | // +---------------+ out[0] = fuaNALUType out[0] |= naluRefIdc // +---------------+ // |0|1|2|3|4|5|6|7| // +-+-+-+-+-+-+-+-+ // |S|E|R| Type | // +---------------+ out[1] = naluType if naluRemaining == naluLength { // Set start bit out[1] |= 1 << 7 } else if naluRemaining-currentFragmentSize == 0 { // Set end bit out[1] |= 1 << 6 } copy(out[fuaHeaderSize:], nalu[naluIndex:naluIndex+currentFragmentSize]) payloads = append(payloads, out) naluRemaining -= currentFragmentSize naluIndex += currentFragmentSize } }) return payloads } // H264Packet represents the H264 header that is stored in the payload of an RTP Packet. type H264Packet struct { IsAVC bool fuaBuffer []byte videoDepacketizer } func (p *H264Packet) doPackaging(buf, nalu []byte) []byte { if p.IsAVC { buf = binary.BigEndian.AppendUint32(buf, uint32(len(nalu))) // nolint: gosec // G115 false positive buf = append(buf, nalu...) return buf } buf = append(buf, annexbNALUStartCode...) buf = append(buf, nalu...) return buf } // IsDetectedFinalPacketInSequence returns true of the packet passed in has the // marker bit set indicated the end of a packet sequence. func (p *H264Packet) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bool { return rtpPacketMarketBit } // Unmarshal parses the passed byte slice and stores the result in the H264Packet this method is called upon. func (p *H264Packet) Unmarshal(payload []byte) ([]byte, error) { if p.zeroAllocation { return payload, nil } return p.parseBody(payload) } func (p *H264Packet) parseBody(payload []byte) ([]byte, error) { //nolint:cyclop if len(payload) == 0 { return nil, fmt.Errorf("%w: %d <=0", errShortPacket, len(payload)) } // NALU Types // https://tools.ietf.org/html/rfc6184#section-5.4 naluType := payload[0] & naluTypeBitmask switch { case naluType > 0 && naluType < 24: return p.doPackaging(nil, payload), nil case naluType == stapaNALUType: currOffset := int(stapaHeaderSize) result := []byte{} for currOffset < len(payload) { naluSizeBytes := payload[currOffset:] if len(naluSizeBytes) < stapaNALULengthSize { break } naluSize := int(binary.BigEndian.Uint16(naluSizeBytes)) currOffset += stapaNALULengthSize if len(payload) < currOffset+naluSize { return nil, fmt.Errorf( "%w STAP-A declared size(%d) is larger than buffer(%d)", errShortPacket, naluSize, len(payload)-currOffset, ) } result = p.doPackaging(result, payload[currOffset:currOffset+naluSize]) currOffset += naluSize } return result, nil case naluType == fuaNALUType: if len(payload) < fuaHeaderSize { return nil, errShortPacket } if p.fuaBuffer == nil { p.fuaBuffer = []byte{} } p.fuaBuffer = append(p.fuaBuffer, payload[fuaHeaderSize:]...) if payload[1]&fuEndBitmask != 0 { naluRefIdc := payload[0] & naluRefIdcBitmask fragmentedNaluType := payload[1] & naluTypeBitmask nalu := append([]byte{}, naluRefIdc|fragmentedNaluType) nalu = append(nalu, p.fuaBuffer...) p.fuaBuffer = nil return p.doPackaging(nil, nalu), nil } return []byte{}, nil } return nil, fmt.Errorf("%w: %d", errUnhandledNALUType, naluType) } // H264PartitionHeadChecker checks H264 partition head. // // Deprecated: replaced by H264Packet.IsPartitionHead(). type H264PartitionHeadChecker struct{} // IsPartitionHead checks if this is the head of a packetized nalu stream. // // Deprecated: replaced by H264Packet.IsPartitionHead(). func (*H264PartitionHeadChecker) IsPartitionHead(packet []byte) bool { return (&H264Packet{}).IsPartitionHead(packet) } // IsPartitionHead checks if this is the head of a packetized nalu stream. func (*H264Packet) IsPartitionHead(payload []byte) bool { if len(payload) < 2 { return false } if payload[0]&naluTypeBitmask == fuaNALUType || payload[0]&naluTypeBitmask == fubNALUType { return payload[1]&fuStartBitmask != 0 } return true } rtp-1.8.27/codecs/h264_packet_test.go000066400000000000000000000220631512154406600172650ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "testing" "github.com/stretchr/testify/assert" ) func TestH264Payloader_Payload(t *testing.T) { pck := H264Payloader{} smallpayload := []byte{0x90, 0x90, 0x90} multiplepayload := []byte{0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x01, 0x90} mixednalupayload := []byte{ 0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x00, 0x01, 0x90, } largepayload := []byte{ 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, } largePayloadPacketized := [][]byte{ {0x1c, 0x80, 0x01, 0x02, 0x03}, {0x1c, 0x00, 0x04, 0x05, 0x06}, {0x1c, 0x00, 0x07, 0x08, 0x09}, {0x1c, 0x00, 0x10, 0x11, 0x12}, {0x1c, 0x40, 0x13, 0x14, 0x15}, } // Positive MTU, nil payload res := pck.Payload(1, nil) assert.Len(t, res, 0, "Generated payload should be empty") // Positive MTU, empty payload res = pck.Payload(1, []byte{}) assert.Len(t, res, 0, "Generated payload should be empty") // Positive MTU, empty NAL res = pck.Payload(1, []byte{0x00, 0x00, 0x01}) assert.Len(t, res, 0, "Generated payload should be empty") // Negative MTU, small payload res = pck.Payload(0, smallpayload) assert.Len(t, res, 0, "Generated payload should be empty") // 0 MTU, small payload res = pck.Payload(0, smallpayload) assert.Len(t, res, 0, "Generated payload should be empty") // Positive MTU, small payload res = pck.Payload(1, smallpayload) assert.Len(t, res, 0, "Generated payload should be empty") // Positive MTU, small payload res = pck.Payload(5, smallpayload) assert.Len(t, res, 1, "Generated payload should be the 1") assert.Len(t, smallpayload, len(res[0]), "Generated payload should be the same size as original payload size") // Multiple NALU in a single payload res = pck.Payload(5, multiplepayload) assert.Len(t, res, 2, "2 nal units should be broken out") for i := 0; i < 2; i++ { assert.Lenf(t, res[i], 1, "Payload %d of 2 is packed incorrectly", i+1) } // Multiple NALU in a single payload with 3-byte and 4-byte start sequences res = pck.Payload(5, mixednalupayload) assert.Len(t, res, 4, "4 nal units should be broken out") for i := 0; i < 4; i++ { assert.Lenf(t, res[i], 1, "Payload %d of 4 is packed incorrectly", i+1) } // Large Payload split across multiple RTP Packets res = pck.Payload(5, largepayload) assert.Equal(t, largePayloadPacketized, res, "FU-A packetization failed") // Nalu type 9 or 12 res = pck.Payload(5, []byte{0x09, 0x00, 0x00}) assert.Len(t, res, 0, "Generated payload should be empty") } func TestH264Packet_Unmarshal(t *testing.T) { singlePayload := []byte{0x90, 0x90, 0x90} singlePayloadUnmarshaled := []byte{0x00, 0x00, 0x00, 0x01, 0x90, 0x90, 0x90} singlePayloadUnmarshaledAVC := []byte{0x00, 0x00, 0x00, 0x03, 0x90, 0x90, 0x90} largepayload := []byte{ 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, } largepayloadAVC := []byte{ 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, } largePayloadPacketized := [][]byte{ {0x1c, 0x80, 0x01, 0x02, 0x03}, {0x1c, 0x00, 0x04, 0x05, 0x06}, {0x1c, 0x00, 0x07, 0x08, 0x09}, {0x1c, 0x00, 0x10, 0x11, 0x12}, {0x1c, 0x40, 0x13, 0x14, 0x15}, } singlePayloadMultiNALU := []byte{ 0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8, } singlePayloadMultiNALUUnmarshaled := []byte{ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x00, 0x00, 0x01, 0x68, 0x1a, 0x34, 0xe3, 0xc8, } singlePayloadMultiNALUUnmarshaledAVC := []byte{ 0x00, 0x00, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x00, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8, } singlePayloadWithBrokenSecondNALU := []byte{ 0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, } singlePayloadWithBrokenSecondNALUUnmarshaled := []byte{ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, } singlePayloadWithBrokenSecondUnmarshaledAVC := []byte{ 0x00, 0x00, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, } incompleteSinglePayloadMultiNALU := []byte{ 0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, } pkt := H264Packet{} avcPkt := H264Packet{IsAVC: true} _, err := pkt.Unmarshal(nil) assert.Error(t, err, "Unmarshal did not fail on nil payload") _, err = pkt.Unmarshal([]byte{}) assert.Error(t, err, "Unmarshal did not fail on []byte{}") _, err = pkt.Unmarshal([]byte{0xFC}) assert.Error(t, err, "Unmarshal accepted a FU-A packet that is too small for a payload and header") _, err = pkt.Unmarshal([]byte{0x0A}) assert.NoError(t, err, "Unmarshaling end of sequence(NALU Type : 10) should succeed") _, err = pkt.Unmarshal([]byte{0xFF, 0x00, 0x00}) assert.Error(t, err, "Unmarshal accepted a packet with a NALU Type we don't handle") _, err = pkt.Unmarshal(incompleteSinglePayloadMultiNALU) assert.Error(t, err, "Unmarshal accepted a STAP-A packet with insufficient data") res, err := pkt.Unmarshal(singlePayload) assert.NoError(t, err) assert.Equal(t, singlePayloadUnmarshaled, res) res, err = avcPkt.Unmarshal(singlePayload) assert.NoError(t, err) assert.Equal(t, singlePayloadUnmarshaledAVC, res) largePayloadResult := []byte{} for i := range largePayloadPacketized { res, err = pkt.Unmarshal(largePayloadPacketized[i]) assert.NoError(t, err) largePayloadResult = append(largePayloadResult, res...) } assert.Equal(t, largepayload, largePayloadResult) largePayloadResultAVC := []byte{} for i := range largePayloadPacketized { res, err = avcPkt.Unmarshal(largePayloadPacketized[i]) assert.NoError(t, err) largePayloadResultAVC = append(largePayloadResultAVC, res...) } assert.Equal(t, largepayloadAVC, largePayloadResultAVC) res, err = pkt.Unmarshal(singlePayloadMultiNALU) assert.NoError(t, err) assert.Equal(t, singlePayloadMultiNALUUnmarshaled, res) res, err = avcPkt.Unmarshal(singlePayloadMultiNALU) assert.NoError(t, err) assert.Equal(t, singlePayloadMultiNALUUnmarshaledAVC, res) res, err = pkt.Unmarshal(singlePayloadWithBrokenSecondNALU) assert.NoError(t, err) assert.Equal(t, singlePayloadWithBrokenSecondNALUUnmarshaled, res) res, err = avcPkt.Unmarshal(singlePayloadWithBrokenSecondNALU) assert.NoError(t, err) assert.Equal(t, singlePayloadWithBrokenSecondUnmarshaledAVC, res) } func TestH264IsPartitionHead(t *testing.T) { h264 := H264Packet{} assert.False(t, h264.IsPartitionHead(nil), "nil must not be a partition head") assert.False(t, h264.IsPartitionHead([]byte{}), "empty nalu must not be a partition head") singleNalu := []byte{1, 0} assert.True(t, h264.IsPartitionHead(singleNalu), "single nalu must be a partition head") stapaNalu := []byte{stapaNALUType, 0} assert.True(t, h264.IsPartitionHead(stapaNalu), "stapa nalu must be a partition head") fuaStartNalu := []byte{fuaNALUType, fuStartBitmask} assert.True(t, h264.IsPartitionHead(fuaStartNalu), "fua start nalu must be a partition head") fuaEndNalu := []byte{fuaNALUType, fuEndBitmask} assert.False(t, h264.IsPartitionHead(fuaEndNalu), "fua end nalu must not be a partition head") fubStartNalu := []byte{fubNALUType, fuStartBitmask} assert.True(t, h264.IsPartitionHead(fubStartNalu), "fub start nalu must be a partition head") fubEndNalu := []byte{fubNALUType, fuEndBitmask} assert.False(t, h264.IsPartitionHead(fubEndNalu), "fub end nalu must not be a partition head") } func TestH264Payloader_Payload_SPS_and_PPS_handling(t *testing.T) { pck := H264Payloader{} expected := [][]byte{ {0x78, 0x00, 0x03, 0x07, 0x00, 0x01, 0x00, 0x03, 0x08, 0x02, 0x03}, {0x05, 0x04, 0x05}, } // When packetizing SPS and PPS are emitted with following NALU res := pck.Payload(1500, []byte{0x07, 0x00, 0x01}) assert.Len(t, res, 0, "Generated payload should be empty") res = pck.Payload(1500, []byte{0x08, 0x02, 0x03}) assert.Len(t, res, 0, "Generated payload should be empty") assert.Equal(t, expected, pck.Payload(1500, []byte{0x05, 0x04, 0x05}), "SPS and PPS aren't packed together") } func TestH264Payloader_Payload_SPS_and_PPS_handling_no_stapA(t *testing.T) { pck := H264Payloader{} pck.DisableStapA = true expectedSps := []byte{0x07, 0x00, 0x01} // The SPS is packed as a single NALU res := pck.Payload(1500, expectedSps) assert.Len(t, res, 1, "Generated payload should not be empty") assert.Equal(t, expectedSps, res[0], "SPS has not been packed correctly") // The PPS is packed as a single NALU expectedPps := []byte{0x08, 0x02, 0x03} res = pck.Payload(1500, expectedPps) assert.Len(t, res, 1, "Generated payload should not be empty") assert.Equal(t, expectedPps, res[0], "PPS has not been packed correctly") } rtp-1.8.27/codecs/h265_packet.go000066400000000000000000001004061512154406600162250ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "encoding/binary" "errors" "fmt" "math" ) // // Errors // var ( errH265CorruptedPacket = errors.New("corrupted h265 packet") errInvalidH265PacketType = errors.New("invalid h265 packet type") errExpectFragmentationStartUnit = errors.New("expecting a fragmentation start unit") ) // // Network Abstraction Unit Header implementation // const ( // sizeof(uint16). h265NaluHeaderSize = 2 // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 h265NaluAggregationPacketType = 48 // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 h265NaluFragmentationUnitType = 49 // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4 h265NaluPACIPacketType = 50 ) // H265NALUHeader is a H265 NAL Unit Header. // https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4 /* * +---------------+---------------+ * |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |F| Type | LayerID | TID | * +-------------+-----------------+ **/ // . type H265NALUHeader uint16 func newH265NALUHeader(highByte, lowByte uint8) H265NALUHeader { return H265NALUHeader((uint16(highByte) << 8) | uint16(lowByte)) } // F is the forbidden bit, should always be 0. func (h H265NALUHeader) F() bool { return (uint16(h) >> 15) != 0 } // Type of NAL Unit. func (h H265NALUHeader) Type() uint8 { // 01111110 00000000 const mask = 0b01111110 << 8 return uint8((uint16(h) & mask) >> (8 + 1)) // nolint: gosec // G115 false positive } // IsTypeVCLUnit returns whether or not the NAL Unit type is a VCL NAL unit. func (h H265NALUHeader) IsTypeVCLUnit() bool { // Type is coded on 6 bits const msbMask = 0b00100000 return (h.Type() & msbMask) == 0 } // LayerID should always be 0 in non-3D HEVC context. func (h H265NALUHeader) LayerID() uint8 { // 00000001 11111000 const mask = (0b00000001 << 8) | 0b11111000 return uint8((uint16(h) & mask) >> 3) // nolint: gosec // G115 false positive } // TID is the temporal identifier of the NAL unit +1. func (h H265NALUHeader) TID() uint8 { const mask = 0b00000111 return uint8(uint16(h) & mask) // nolint: gosec // G115 false positive } // IsAggregationPacket returns whether or not the packet is an Aggregation packet. func (h H265NALUHeader) IsAggregationPacket() bool { return h.Type() == h265NaluAggregationPacketType } // IsFragmentationUnit returns whether or not the packet is a Fragmentation Unit packet. func (h H265NALUHeader) IsFragmentationUnit() bool { return h.Type() == h265NaluFragmentationUnitType } // IsPACIPacket returns whether or not the packet is a PACI packet. func (h H265NALUHeader) IsPACIPacket() bool { return h.Type() == h265NaluPACIPacketType } // // Single NAL Unit Packet implementation // // H265SingleNALUnitPacket represents a NALU packet, containing exactly one NAL unit. /* * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PayloadHdr | DONL (conditional) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * | NAL unit payload data | * | | * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | :...OPTIONAL RTP padding | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ **/ // Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.1 type H265SingleNALUnitPacket struct { // payloadHeader is the header of the H265 packet. payloadHeader H265NALUHeader // donl is a 16-bit field, that may or may not be present. donl *uint16 // payload of the fragmentation unit. payload []byte mightNeedDONL bool } // WithDONL can be called to specify whether or not DONL might be parsed. // DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. func (p *H265SingleNALUnitPacket) WithDONL(value bool) { p.mightNeedDONL = value } // Unmarshal parses the passed byte slice and stores the result in the H265SingleNALUnitPacket // this method is called upon. func (p *H265SingleNALUnitPacket) Unmarshal(payload []byte) ([]byte, error) { // sizeof(headers) const totalHeaderSize = h265NaluHeaderSize if payload == nil { return nil, errNilPacket } else if len(payload) <= totalHeaderSize { return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) } payloadHeader := newH265NALUHeader(payload[0], payload[1]) if payloadHeader.F() { return nil, errH265CorruptedPacket } if payloadHeader.IsFragmentationUnit() || payloadHeader.IsPACIPacket() || payloadHeader.IsAggregationPacket() { return nil, errInvalidH265PacketType } payload = payload[2:] if p.mightNeedDONL { // sizeof(uint16) if len(payload) <= 2 { return nil, errShortPacket } donl := (uint16(payload[0]) << 8) | uint16(payload[1]) p.donl = &donl payload = payload[2:] } p.payloadHeader = payloadHeader p.payload = payload return nil, nil } // PayloadHeader returns the NALU header of the packet. func (p *H265SingleNALUnitPacket) PayloadHeader() H265NALUHeader { return p.payloadHeader } // DONL returns the DONL of the packet. func (p *H265SingleNALUnitPacket) DONL() *uint16 { return p.donl } // Payload returns the Fragmentation Unit packet payload. func (p *H265SingleNALUnitPacket) Payload() []byte { return p.payload } func (p *H265SingleNALUnitPacket) isH265Packet() {} func (p *H265SingleNALUnitPacket) doPackaging(buf []byte) []byte { buf = append(buf, annexbNALUStartCode...) buf = append(buf, byte(p.payloadHeader>>8), byte(p.payloadHeader&0xFF)) buf = append(buf, p.payload...) return buf } // // Aggregation Packets implementation // // H265AggregationUnitFirst represent the First Aggregation Unit in an AP. /* * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * : DONL (conditional) | NALU size | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NALU size | | * +-+-+-+-+-+-+-+-+ NAL unit | * | | * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ **/ // Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 type H265AggregationUnitFirst struct { donl *uint16 nalUnitSize uint16 nalUnit []byte } // DONL field, when present, specifies the value of the 16 least // significant bits of the decoding order number of the aggregated NAL // unit. func (u H265AggregationUnitFirst) DONL() *uint16 { return u.donl } // NALUSize represents the size, in bytes, of the NalUnit. func (u H265AggregationUnitFirst) NALUSize() uint16 { return u.nalUnitSize } // NalUnit payload. func (u H265AggregationUnitFirst) NalUnit() []byte { return u.nalUnit } // H265AggregationUnit represent the an Aggregation Unit in an AP, which is not the first one. /* * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * : DOND (cond) | NALU size | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * | NAL unit | * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | : * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ **/ // Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 type H265AggregationUnit struct { dond *uint8 nalUnitSize uint16 nalUnit []byte } // DOND field plus 1 specifies the difference between // the decoding order number values of the current aggregated NAL unit // and the preceding aggregated NAL unit in the same AP. func (u H265AggregationUnit) DOND() *uint8 { return u.dond } // NALUSize represents the size, in bytes, of the NalUnit. func (u H265AggregationUnit) NALUSize() uint16 { return u.nalUnitSize } // NalUnit payload. func (u H265AggregationUnit) NalUnit() []byte { return u.nalUnit } // H265AggregationPacket represents an Aggregation packet. /* * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PayloadHdr (Type=48) | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | * | | * | two or more aggregation units | * | | * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | :...OPTIONAL RTP padding | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ **/ // Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 type H265AggregationPacket struct { firstUnit *H265AggregationUnitFirst otherUnits []H265AggregationUnit mightNeedDONL bool } // WithDONL can be called to specify whether or not DONL might be parsed. // DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. func (p *H265AggregationPacket) WithDONL(value bool) { p.mightNeedDONL = value } // Unmarshal parses the passed byte slice and stores the result in the H265AggregationPacket this method is called upon. func (p *H265AggregationPacket) Unmarshal(payload []byte) ([]byte, error) { //nolint:cyclop // sizeof(headers) const totalHeaderSize = h265NaluHeaderSize if payload == nil { return nil, errNilPacket } else if len(payload) <= totalHeaderSize { return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) } payloadHeader := newH265NALUHeader(payload[0], payload[1]) if payloadHeader.F() { return nil, errH265CorruptedPacket } if !payloadHeader.IsAggregationPacket() { return nil, errInvalidH265PacketType } // First parse the first aggregation unit payload = payload[2:] firstUnit := &H265AggregationUnitFirst{} if p.mightNeedDONL { if len(payload) < 2 { return nil, errShortPacket } donl := (uint16(payload[0]) << 8) | uint16(payload[1]) firstUnit.donl = &donl payload = payload[2:] } if len(payload) < 2 { return nil, errShortPacket } firstUnit.nalUnitSize = (uint16(payload[0]) << 8) | uint16(payload[1]) payload = payload[2:] if len(payload) < int(firstUnit.nalUnitSize) { return nil, errShortPacket } firstUnit.nalUnit = payload[:firstUnit.nalUnitSize] payload = payload[firstUnit.nalUnitSize:] // Parse remaining Aggregation Units var units []H265AggregationUnit for { unit := H265AggregationUnit{} if p.mightNeedDONL { if len(payload) < 1 { break } dond := payload[0] unit.dond = &dond payload = payload[1:] } if len(payload) < 2 { break } unit.nalUnitSize = (uint16(payload[0]) << 8) | uint16(payload[1]) payload = payload[2:] if len(payload) < int(unit.nalUnitSize) { break } unit.nalUnit = payload[:unit.nalUnitSize] payload = payload[unit.nalUnitSize:] units = append(units, unit) } // There need to be **at least** two Aggregation Units (first + another one) if len(units) == 0 { return nil, errShortPacket } p.firstUnit = firstUnit p.otherUnits = units return nil, nil } // FirstUnit returns the first Aggregated Unit of the packet. func (p *H265AggregationPacket) FirstUnit() *H265AggregationUnitFirst { return p.firstUnit } // OtherUnits returns the all the other Aggregated Unit of the packet (excluding the first one). func (p *H265AggregationPacket) OtherUnits() []H265AggregationUnit { return p.otherUnits } func (p *H265AggregationPacket) isH265Packet() {} func (p *H265AggregationPacket) doPackaging(buf []byte) []byte { if p.firstUnit == nil { return buf } buf = append(buf, annexbNALUStartCode...) buf = append(buf, p.firstUnit.nalUnit...) for _, unit := range p.otherUnits { buf = append(buf, annexbNALUStartCode...) buf = append(buf, unit.nalUnit...) } return buf } // // Fragmentation Unit implementation // const ( // sizeof(uint8). h265FragmentationUnitHeaderSize = 1 ) // H265FragmentationUnitHeader is a H265 FU Header. // // +---------------+ // |0|1|2|3|4|5|6|7| // +-+-+-+-+-+-+-+-+ // |S|E| FuType | // +---------------+ // . type H265FragmentationUnitHeader uint8 // S represents the start of a fragmented NAL unit. func (h H265FragmentationUnitHeader) S() bool { const mask = 0b10000000 return ((h & mask) >> 7) != 0 } // E represents the end of a fragmented NAL unit. func (h H265FragmentationUnitHeader) E() bool { const mask = 0b01000000 return ((h & mask) >> 6) != 0 } // FuType MUST be equal to the field Type of the fragmented NAL unit. func (h H265FragmentationUnitHeader) FuType() uint8 { const mask = 0b00111111 return uint8(h) & mask } // H265FragmentationUnitPacket represents a single Fragmentation Unit packet. /* * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PayloadHdr (Type=49) | FU header | DONL (cond) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| * | DONL (cond) | | * |-+-+-+-+-+-+-+-+ | * | FU payload | * | | * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | :...OPTIONAL RTP padding | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ **/ // Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 type H265FragmentationUnitPacket struct { // payloadHeader is the header of the H265 packet. payloadHeader H265NALUHeader // fuHeader is the header of the fragmentation unit fuHeader H265FragmentationUnitHeader // donl is a 16-bit field, that may or may not be present. donl *uint16 // payload of the fragmentation unit. payload []byte mightNeedDONL bool } // WithDONL can be called to specify whether or not DONL might be parsed. // DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. func (p *H265FragmentationUnitPacket) WithDONL(value bool) { p.mightNeedDONL = value } // Unmarshal parses the passed byte slice and stores the result in the H265FragmentationUnitPacket // this method is called upon. func (p *H265FragmentationUnitPacket) Unmarshal(payload []byte) ([]byte, error) { // sizeof(headers) const totalHeaderSize = h265NaluHeaderSize + h265FragmentationUnitHeaderSize if payload == nil { return nil, errNilPacket } else if len(payload) <= totalHeaderSize { return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) } payloadHeader := newH265NALUHeader(payload[0], payload[1]) if payloadHeader.F() { return nil, errH265CorruptedPacket } if !payloadHeader.IsFragmentationUnit() { return nil, errInvalidH265PacketType } fuHeader := H265FragmentationUnitHeader(payload[2]) payload = payload[3:] if fuHeader.S() && p.mightNeedDONL { // sizeof(uint16) if len(payload) <= 2 { return nil, errShortPacket } donl := (uint16(payload[0]) << 8) | uint16(payload[1]) p.donl = &donl payload = payload[2:] } p.payloadHeader = payloadHeader p.fuHeader = fuHeader p.payload = payload return nil, nil } // PayloadHeader returns the NALU header of the packet. func (p *H265FragmentationUnitPacket) PayloadHeader() H265NALUHeader { return p.payloadHeader } // FuHeader returns the Fragmentation Unit Header of the packet. func (p *H265FragmentationUnitPacket) FuHeader() H265FragmentationUnitHeader { return p.fuHeader } // DONL returns the DONL of the packet. func (p *H265FragmentationUnitPacket) DONL() *uint16 { return p.donl } // Payload returns the Fragmentation Unit packet payload. func (p *H265FragmentationUnitPacket) Payload() []byte { return p.payload } func (p *H265FragmentationUnitPacket) isH265Packet() {} // H265FragmentationPacket represents a Fragmentation packet, which contains one or more Fragmentation Units. type H265FragmentationPacket struct { payloadHeader H265NALUHeader donl *uint16 units []*H265FragmentationUnitPacket payload []byte } // NewH265FragmentationPacket creates a H265FragmentationPacket. func NewH265FragmentationPacket(startUnit *H265FragmentationUnitPacket) *H265FragmentationPacket { return &H265FragmentationPacket{ payloadHeader: (startUnit.payloadHeader & 0x81FF) | (H265NALUHeader(startUnit.FuHeader().FuType()) << 9), donl: startUnit.donl, units: []*H265FragmentationUnitPacket{startUnit}, } } // PayloadHeader returns the NALU header of the packet. func (p *H265FragmentationPacket) PayloadHeader() H265NALUHeader { return p.payloadHeader } // DONL returns the DONL of the packet. func (p *H265FragmentationPacket) DONL() *uint16 { return p.donl } // Payload returns the Fragmentation packet payload. func (p *H265FragmentationPacket) Payload() []byte { return p.payload } func (p *H265FragmentationPacket) isH265Packet() {} func (p *H265FragmentationPacket) doPackaging(buf []byte) []byte { if len(p.payload) == 0 { return buf } buf = append(buf, annexbNALUStartCode...) buf = append(buf, byte(p.payloadHeader>>8), byte(p.payloadHeader&0xFF)) buf = append(buf, p.payload...) return buf } func (p *H265FragmentationPacket) appendUnit(unit *H265FragmentationUnitPacket) { if len(p.payload) > 0 { // already have end unit return } p.units = append(p.units, unit) if unit.FuHeader().E() { for _, u := range p.units { p.payload = append(p.payload, u.payload...) } } } // // PACI implementation // // H265PACIPacket represents a single H265 PACI packet. /* * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PayloadHdr (Type=50) |A| cType | PHSsize |F0..2|Y| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Payload Header Extension Structure (PHES) | * |=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=| * | | * | PACI payload: NAL unit | * | . . . | * | | * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | :...OPTIONAL RTP padding | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ **/ // Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4 type H265PACIPacket struct { // payloadHeader is the header of the H265 packet. payloadHeader H265NALUHeader // Field which holds value for `A`, `cType`, `PHSsize`, `F0`, `F1`, `F2` and `Y` fields. paciHeaderFields uint16 // phes is a header extension, of byte length `PHSsize` phes []byte // Payload contains NAL units & optional padding payload []byte } // PayloadHeader returns the NAL Unit Header. func (p *H265PACIPacket) PayloadHeader() H265NALUHeader { return p.payloadHeader } // A copies the F bit of the PACI payload NALU. func (p *H265PACIPacket) A() bool { const mask = 0b10000000 << 8 return (p.paciHeaderFields & mask) != 0 } // CType copies the Type field of the PACI payload NALU. func (p *H265PACIPacket) CType() uint8 { const mask = 0b01111110 << 8 return uint8((p.paciHeaderFields & mask) >> (8 + 1)) // nolint: gosec // G115 false positive } // PHSsize indicates the size of the PHES field. func (p *H265PACIPacket) PHSsize() uint8 { const mask = (0b00000001 << 8) | 0b11110000 return uint8((p.paciHeaderFields & mask) >> 4) // nolint: gosec // G115 false positive } // F0 indicates the presence of a Temporal Scalability support extension in the PHES. func (p *H265PACIPacket) F0() bool { const mask = 0b00001000 return (p.paciHeaderFields & mask) != 0 } // F1 must be zero, reserved for future extensions. func (p *H265PACIPacket) F1() bool { const mask = 0b00000100 return (p.paciHeaderFields & mask) != 0 } // F2 must be zero, reserved for future extensions. func (p *H265PACIPacket) F2() bool { const mask = 0b00000010 return (p.paciHeaderFields & mask) != 0 } // Y must be zero, reserved for future extensions. func (p *H265PACIPacket) Y() bool { const mask = 0b00000001 return (p.paciHeaderFields & mask) != 0 } // PHES contains header extensions. Its size is indicated by PHSsize. func (p *H265PACIPacket) PHES() []byte { return p.phes } // Payload is a single NALU or NALU-like struct, not including the first two octets (header). func (p *H265PACIPacket) Payload() []byte { return p.payload } // TSCI returns the Temporal Scalability Control Information extension, if present. func (p *H265PACIPacket) TSCI() *H265TSCI { if !p.F0() || p.PHSsize() < 3 { return nil } tsci := H265TSCI((uint32(p.phes[0]) << 16) | (uint32(p.phes[1]) << 8) | uint32(p.phes[0])) return &tsci } // Unmarshal parses the passed byte slice and stores the result in the H265PACIPacket this method is called upon. func (p *H265PACIPacket) Unmarshal(payload []byte) ([]byte, error) { // sizeof(headers) const totalHeaderSize = h265NaluHeaderSize + 2 if payload == nil { return nil, errNilPacket } else if len(payload) <= totalHeaderSize { return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) } payloadHeader := newH265NALUHeader(payload[0], payload[1]) if payloadHeader.F() { return nil, errH265CorruptedPacket } if !payloadHeader.IsPACIPacket() { return nil, errInvalidH265PacketType } paciHeaderFields := (uint16(payload[2]) << 8) | uint16(payload[3]) payload = payload[4:] p.paciHeaderFields = paciHeaderFields headerExtensionSize := p.PHSsize() if len(payload) < int(headerExtensionSize)+1 { p.paciHeaderFields = 0 return nil, errShortPacket } p.payloadHeader = payloadHeader if headerExtensionSize > 0 { p.phes = payload[:headerExtensionSize] } payload = payload[headerExtensionSize:] p.payload = payload return nil, nil } func (p *H265PACIPacket) isH265Packet() {} func (p *H265PACIPacket) doPackaging(buf []byte) []byte { buf = append(buf, annexbNALUStartCode...) buf = append(buf, byte(p.payloadHeader>>8), byte(p.payloadHeader&0xFF)) buf = binary.BigEndian.AppendUint16(buf, p.paciHeaderFields) if len(p.phes) > 0 { buf = append(buf, p.phes...) } buf = append(buf, p.payload...) return buf } // // Temporal Scalability Control Information // // H265TSCI is a Temporal Scalability Control Information header extension. // Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.5 type H265TSCI uint32 // TL0PICIDX see RFC7798 for more details. func (h H265TSCI) TL0PICIDX() uint8 { const m1 = 0xFFFF0000 const m2 = 0xFF00 return uint8((((h & m1) >> 16) & m2) >> 8) // nolint: gosec // G115 false positive } // IrapPicID see RFC7798 for more details. func (h H265TSCI) IrapPicID() uint8 { const m1 = 0xFFFF0000 const m2 = 0x00FF return uint8(((h & m1) >> 16) & m2) // nolint: gosec // G115 false positive } // S see RFC7798 for more details. func (h H265TSCI) S() bool { const m1 = 0xFF00 const m2 = 0b10000000 return (uint8((h&m1)>>8) & m2) != 0 // nolint: gosec // G115 false positive } // E see RFC7798 for more details. func (h H265TSCI) E() bool { const m1 = 0xFF00 const m2 = 0b01000000 return (uint8((h&m1)>>8) & m2) != 0 // nolint: gosec // G115 false positive } // RES see RFC7798 for more details. func (h H265TSCI) RES() uint8 { const m1 = 0xFF00 const m2 = 0b00111111 return uint8((h&m1)>>8) & m2 // nolint: gosec // G115 false positive } // // H265 Packet interface // type isH265Packet interface { isH265Packet() doPackaging([]byte) []byte } var ( _ isH265Packet = (*H265FragmentationPacket)(nil) _ isH265Packet = (*H265PACIPacket)(nil) _ isH265Packet = (*H265SingleNALUnitPacket)(nil) _ isH265Packet = (*H265AggregationPacket)(nil) ) // // Packet implementation // // H265Packet represents a H265 packet, stored in the payload of an RTP packet. type H265Packet struct { packet isH265Packet mightNeedDONL bool videoDepacketizer } // WithDONL can be called to specify whether or not DONL might be parsed. // DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. func (p *H265Packet) WithDONL(value bool) { p.mightNeedDONL = value } // Unmarshal parses the passed byte slice and stores the result in the H265Packet this method is called upon. func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { // nolint:cyclop if payload == nil { return nil, errNilPacket } else if len(payload) <= h265NaluHeaderSize { return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), h265NaluHeaderSize) } payloadHeader := newH265NALUHeader(payload[0], payload[1]) if payloadHeader.F() { return nil, errH265CorruptedPacket } switch { case payloadHeader.IsPACIPacket(): decoded := &H265PACIPacket{} if _, err := decoded.Unmarshal(payload); err != nil { return nil, err } p.packet = decoded case payloadHeader.IsFragmentationUnit(): decoded := &H265FragmentationUnitPacket{} decoded.WithDONL(p.mightNeedDONL) if _, err := decoded.Unmarshal(payload); err != nil { return nil, err } if decoded.FuHeader().S() { p.packet = NewH265FragmentationPacket(decoded) } else { fu, ok := p.packet.(*H265FragmentationPacket) if !ok { return nil, errExpectFragmentationStartUnit } fu.appendUnit(decoded) } case payloadHeader.IsAggregationPacket(): decoded := &H265AggregationPacket{} decoded.WithDONL(p.mightNeedDONL) if _, err := decoded.Unmarshal(payload); err != nil { return nil, err } p.packet = decoded default: decoded := &H265SingleNALUnitPacket{} decoded.WithDONL(p.mightNeedDONL) if _, err := decoded.Unmarshal(payload); err != nil { return nil, err } p.packet = decoded } return p.packet.doPackaging(nil), nil } // Packet returns the populated packet. // Must be casted to one of: // - *H265SingleNALUnitPacket // - *H265FragmentationUnitPacket // - *H265AggregationPacket // - *H265PACIPacket. func (p *H265Packet) Packet() isH265Packet { return p.packet } // IsPartitionHead checks if this is the head of a packetized nalu stream. func (*H265Packet) IsPartitionHead(payload []byte) bool { if len(payload) < 3 { return false } if H265NALUHeader(binary.BigEndian.Uint16(payload[0:2])).Type() == h265NaluFragmentationUnitType { return H265FragmentationUnitHeader(payload[2]).S() } return true } // H265Payloader payloads H265 packets. type H265Payloader struct { AddDONL bool SkipAggregation bool donl uint16 } // Payload fragments a H265 packet across one or more byte arrays. func (p *H265Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint:gocognit,cyclop var payloads [][]byte if len(payload) == 0 || mtu == 0 { return payloads } bufferedNALUs := make([][]byte, 0) aggregationBufferSize := 0 flushBufferedNals := func() { if len(bufferedNALUs) == 0 { return } if len(bufferedNALUs) == 1 { //nolint:nestif // emit this as a single NALU packet nalu := bufferedNALUs[0] if p.AddDONL { buf := make([]byte, len(nalu)+2) // copy the NALU header to the payload header copy(buf[0:h265NaluHeaderSize], nalu[0:h265NaluHeaderSize]) // copy the DONL into the header binary.BigEndian.PutUint16(buf[h265NaluHeaderSize:h265NaluHeaderSize+2], p.donl) // write the payload copy(buf[h265NaluHeaderSize+2:], nalu[h265NaluHeaderSize:]) p.donl++ payloads = append(payloads, buf) } else { // write the nalu directly to the payload payloads = append(payloads, nalu) } } else { // construct an aggregation packet aggregationPacketSize := aggregationBufferSize buf := make([]byte, aggregationPacketSize) layerID := uint8(math.MaxUint8) tid := uint8(math.MaxUint8) for _, nalu := range bufferedNALUs { header := newH265NALUHeader(nalu[0], nalu[1]) headerLayerID := header.LayerID() headerTID := header.TID() if headerLayerID < layerID { layerID = headerLayerID } if headerTID < tid { tid = headerTID } } binary.BigEndian.PutUint16(buf[0:2], (uint16(h265NaluAggregationPacketType)<<9)|(uint16(layerID)<<3)|uint16(tid)) index := 2 for i, nalu := range bufferedNALUs { if p.AddDONL { if i == 0 { binary.BigEndian.PutUint16(buf[index:index+2], p.donl) index += 2 } else { buf[index] = byte(i - 1) index++ } } // Since the type of mtu is uint16, len(nalu) fits in as well, so it is safe. // #nosec binary.BigEndian.PutUint16(buf[index:index+2], uint16(len(nalu))) index += 2 index += copy(buf[index:], nalu) } payloads = append(payloads, buf) } // clear the buffered NALUs bufferedNALUs = make([][]byte, 0) aggregationBufferSize = 0 } calcMarginalAggregationSize := func(nalu []byte) int { marginalAggregationSize := len(nalu) + 2 // +2 is NALU size Field size if len(bufferedNALUs) == 1 { marginalAggregationSize = len(nalu) + 4 // +4 are Aggregation header + NALU size Field size } if p.AddDONL { if len(bufferedNALUs) == 0 { marginalAggregationSize += 2 } else { marginalAggregationSize++ } } return marginalAggregationSize } emitNalus(payload, func(nalu []byte) { if len(nalu) < 2 { // NALU header is 2 bytes return } naluLen := len(nalu) + 2 if p.AddDONL { naluLen += 2 } if naluLen <= int(mtu) { //nolint:nestif // this nalu fits into a single packet, either it can be emitted as // a single nalu or appended to the previous aggregation packet marginalAggregationSize := calcMarginalAggregationSize(nalu) if aggregationBufferSize+marginalAggregationSize > int(mtu) { flushBufferedNals() marginalAggregationSize = calcMarginalAggregationSize(nalu) } bufferedNALUs = append(bufferedNALUs, nalu) aggregationBufferSize += marginalAggregationSize if p.SkipAggregation { // emit this immediately. flushBufferedNals() } } else { // if this nalu doesn't fit in the current mtu, it needs to be fragmented fuPacketHeaderSize := h265FragmentationUnitHeaderSize + 2 /* payload header size */ if p.AddDONL { fuPacketHeaderSize += 2 } // then, fragment the nalu maxFUPayloadSize := int(mtu) - fuPacketHeaderSize naluHeader := newH265NALUHeader(nalu[0], nalu[1]) // the nalu header is omitted from the fragmentation packet payload nalu = nalu[h265NaluHeaderSize:] if maxFUPayloadSize <= 0 || len(nalu) == 0 { return } // flush any buffered aggregation packets. flushBufferedNals() fullNALUSize := len(nalu) for len(nalu) > 0 { curentFUPayloadSize := min(len(nalu), maxFUPayloadSize) out := make([]byte, fuPacketHeaderSize+curentFUPayloadSize) // write the payload header binary.BigEndian.PutUint16(out[0:2], uint16(naluHeader)) out[0] = (out[0] & 0b10000001) | h265NaluFragmentationUnitType<<1 // write the fragment header out[2] = byte(H265FragmentationUnitHeader(naluHeader.Type())) if len(nalu) == fullNALUSize { // Set start bit out[2] |= 1 << 7 } else if len(nalu)-curentFUPayloadSize == 0 { // Set end bit out[2] |= 1 << 6 } if p.AddDONL { // write the DONL header binary.BigEndian.PutUint16(out[3:5], p.donl) p.donl++ // copy the fragment payload copy(out[5:], nalu[0:curentFUPayloadSize]) } else { // copy the fragment payload copy(out[3:], nalu[0:curentFUPayloadSize]) } // append the fragment to the payload payloads = append(payloads, out) // advance the nalu data pointer nalu = nalu[curentFUPayloadSize:] } } }) flushBufferedNals() return payloads } rtp-1.8.27/codecs/h265_packet_test.go000066400000000000000000001316441512154406600172740ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "errors" "testing" "github.com/stretchr/testify/assert" ) func TestH265_NALU_Header(t *testing.T) { tt := [...]struct { RawHeader []byte FBit bool Type uint8 LayerID uint8 TID uint8 IsAP bool IsFU bool IsPACI bool }{ // FBit { RawHeader: []byte{0x80, 0x00}, Type: 0, LayerID: 0, TID: 0, FBit: true, }, // VPS_NUT { RawHeader: []byte{0x40, 0x01}, Type: 32, LayerID: 0, TID: 1, }, // SPS_NUT { RawHeader: []byte{0x42, 0x01}, Type: 33, LayerID: 0, TID: 1, }, // PPS_NUT { RawHeader: []byte{0x44, 0x01}, Type: 34, LayerID: 0, TID: 1, }, // PREFIX_SEI_NUT { RawHeader: []byte{0x4e, 0x01}, Type: 39, LayerID: 0, TID: 1, }, // Fragmentation Unit { RawHeader: []byte{0x62, 0x01}, Type: h265NaluFragmentationUnitType, LayerID: 0, TID: 1, IsFU: true, }, } for _, cur := range tt { header := newH265NALUHeader(cur.RawHeader[0], cur.RawHeader[1]) assert.Equal(t, cur.FBit, header.F()) assert.Equal(t, cur.Type, header.Type()) // For any type < 32, NAL is a VLC NAL unit. assert.Equal(t, header.IsTypeVCLUnit(), header.Type() < 32) assert.Equal(t, cur.IsAP, header.IsAggregationPacket()) assert.Equal(t, cur.IsFU, header.IsFragmentationUnit()) assert.Equal(t, cur.IsPACI, header.IsPACIPacket()) assert.Equal(t, cur.LayerID, header.LayerID()) assert.Equal(t, cur.TID, header.TID()) } } func TestH265_FU_Header(t *testing.T) { tt := [...]struct { header H265FragmentationUnitHeader S bool E bool Type uint8 }{ // Start | IDR_W_RADL { header: H265FragmentationUnitHeader(0x93), S: true, E: false, Type: 19, }, // Continuation | IDR_W_RADL { header: H265FragmentationUnitHeader(0x13), S: false, E: false, Type: 19, }, // End | IDR_W_RADL { header: H265FragmentationUnitHeader(0x53), S: false, E: true, Type: 19, }, // Start | TRAIL_R { header: H265FragmentationUnitHeader(0x81), S: true, E: false, Type: 1, }, // Continuation | TRAIL_R { header: H265FragmentationUnitHeader(0x01), S: false, E: false, Type: 1, }, // End | TRAIL_R { header: H265FragmentationUnitHeader(0x41), S: false, E: true, Type: 1, }, } for _, cur := range tt { assert.Equal(t, cur.S, cur.header.S()) assert.Equal(t, cur.E, cur.header.E()) assert.Equal(t, cur.header.FuType(), cur.Type) } } func TestH265_SingleNALUnitPacket(t *testing.T) { tt := [...]struct { Raw []byte WithDONL bool ExpectedPacket *H265SingleNALUnitPacket ExpectedErr error }{ { Raw: nil, ExpectedErr: errNilPacket, }, { Raw: []byte{}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93}, ExpectedErr: errInvalidH265PacketType, }, // FBit enabled in H265NALUHeader { Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Type '49' in H265NALUHeader { Raw: []byte{0x62, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errInvalidH265PacketType, }, // Type '50' in H265NALUHeader { Raw: []byte{0x64, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errInvalidH265PacketType, }, { Raw: []byte{0x01, 0x01, 0xab, 0xcd, 0xef}, ExpectedPacket: &H265SingleNALUnitPacket{ payloadHeader: newH265NALUHeader(0x01, 0x01), payload: []byte{0xab, 0xcd, 0xef}, }, }, // DONL, payload too small { Raw: []byte{0x01, 0x01, 0x93, 0xaf}, ExpectedErr: errShortPacket, WithDONL: true, }, { Raw: []byte{0x01, 0x01, 0xaa, 0xbb, 0xcc}, ExpectedPacket: &H265SingleNALUnitPacket{ payloadHeader: newH265NALUHeader(0x01, 0x01), donl: uint16ptr((uint16(0xaa) << 8) | uint16(0xbb)), payload: []byte{0xcc}, }, WithDONL: true, }, } for _, cur := range tt { parsed := &H265SingleNALUnitPacket{} if cur.WithDONL { parsed.WithDONL(cur.WithDONL) } // Just for code coverage sake parsed.isH265Packet() _, err := parsed.Unmarshal(cur.Raw) if cur.ExpectedErr == nil { assert.NoError(t, err) } else { assert.ErrorIs(t, err, cur.ExpectedErr) } if cur.ExpectedPacket == nil { continue } assert.Equal(t, cur.ExpectedPacket.PayloadHeader(), parsed.PayloadHeader()) if cur.ExpectedPacket.DONL() != nil { assert.Equal(t, *cur.ExpectedPacket.DONL(), *parsed.DONL()) } else { assert.Nil(t, parsed.DONL()) } assert.Equal(t, cur.ExpectedPacket.Payload(), parsed.Payload()) } } func TestH265_AggregationPacket(t *testing.T) { tt := [...]struct { Raw []byte WithDONL bool ExpectedPacket *H265AggregationPacket ExpectedErr error }{ { Raw: nil, ExpectedErr: errNilPacket, }, { Raw: []byte{}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93}, ExpectedErr: errInvalidH265PacketType, }, // FBit enabled in H265NALUHeader { Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Type '48' in H265NALUHeader { Raw: []byte{0xE0, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Small payload { Raw: []byte{0x60, 0x01, 0x00, 0x1}, ExpectedErr: errShortPacket, }, // Small payload { Raw: []byte{0x60, 0x01, 0x00}, ExpectedErr: errShortPacket, WithDONL: true, }, // Small payload { Raw: []byte{0x60, 0x01, 0x00, 0x1}, ExpectedErr: errShortPacket, WithDONL: true, }, // Small payload { Raw: []byte{0x60, 0x01, 0x00, 0x01, 0x02}, ExpectedErr: errShortPacket, WithDONL: true, }, // Single Aggregation Unit { Raw: []byte{0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00}, ExpectedErr: errShortPacket, WithDONL: true, }, // Incomplete second Aggregation Unit { Raw: []byte{ 0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, // DONL 0x00, }, ExpectedErr: errShortPacket, WithDONL: true, }, // Incomplete second Aggregation Unit { Raw: []byte{ 0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, // DONL, NAL Unit size (2 bytes) 0x00, 0x55, 0x55, }, ExpectedErr: errShortPacket, WithDONL: true, }, // Valid Second Aggregation Unit { Raw: []byte{ 0x60, 0x01, 0xcc, 0xdd, 0x00, 0x02, 0xff, 0xee, // DONL, NAL Unit size (2 bytes), Payload 0x77, 0x00, 0x01, 0xaa, }, WithDONL: true, ExpectedPacket: &H265AggregationPacket{ firstUnit: &H265AggregationUnitFirst{ donl: uint16ptr(0xccdd), nalUnitSize: 2, nalUnit: []byte{0xff, 0xee}, }, otherUnits: []H265AggregationUnit{ { dond: uint8ptr(0x77), nalUnitSize: 1, nalUnit: []byte{0xaa}, }, }, }, }, } for _, cur := range tt { parsed := &H265AggregationPacket{} if cur.WithDONL { parsed.WithDONL(cur.WithDONL) } // Just for code coverage sake parsed.isH265Packet() _, err := parsed.Unmarshal(cur.Raw) if cur.ExpectedErr == nil { assert.NoError(t, err) } else { assert.ErrorIs(t, err, cur.ExpectedErr) } if cur.ExpectedPacket == nil { continue } if cur.ExpectedPacket.FirstUnit() != nil { assert.Equal(t, cur.ExpectedPacket.FirstUnit().NALUSize(), parsed.FirstUnit().NALUSize()) if cur.ExpectedPacket.FirstUnit().DONL() != nil { assert.Equal(t, *cur.ExpectedPacket.FirstUnit().DONL(), *parsed.FirstUnit().DONL()) } else { assert.Nil(t, parsed.FirstUnit().DONL()) } assert.Equal( t, cur.ExpectedPacket.FirstUnit().NalUnit(), parsed.FirstUnit().NalUnit(), ) } assert.Len(t, cur.ExpectedPacket.OtherUnits(), len(parsed.OtherUnits())) for ndx, unit := range cur.ExpectedPacket.OtherUnits() { assert.Equal(t, unit.NALUSize(), parsed.OtherUnits()[ndx].NALUSize()) if unit.DOND() != nil { assert.Equal(t, *unit.DOND(), *parsed.OtherUnits()[ndx].DOND()) } else { assert.Nil(t, parsed.OtherUnits()[ndx].DOND()) } assert.Equal(t, unit.NalUnit(), parsed.OtherUnits()[ndx].NalUnit()) } assert.Equal(t, cur.ExpectedPacket.OtherUnits(), parsed.OtherUnits()) } } func TestH265_FragmentationUnitPacket(t *testing.T) { tt := [...]struct { Raw []byte WithDONL bool ExpectedFU *H265FragmentationUnitPacket ExpectedErr error }{ { Raw: nil, ExpectedErr: errNilPacket, }, { Raw: []byte{}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93}, ExpectedErr: errShortPacket, }, // FBit enabled in H265NALUHeader { Raw: []byte{0x80, 0x01, 0x93, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Type not '49' in H265NALUHeader { Raw: []byte{0x40, 0x01, 0x93, 0xaf}, ExpectedErr: errInvalidH265PacketType, }, { Raw: []byte{0x62, 0x01, 0x93, 0xaf}, ExpectedFU: &H265FragmentationUnitPacket{ payloadHeader: newH265NALUHeader(0x62, 0x01), fuHeader: H265FragmentationUnitHeader(0x93), donl: nil, payload: []byte{0xaf}, }, }, { Raw: []byte{0x62, 0x01, 0x93, 0xcc}, WithDONL: true, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93, 0xcc, 0xdd, 0xaf, 0x0d, 0x5a}, WithDONL: true, ExpectedFU: &H265FragmentationUnitPacket{ payloadHeader: newH265NALUHeader(0x62, 0x01), fuHeader: H265FragmentationUnitHeader(0x93), donl: uint16ptr((uint16(0xcc) << 8) | uint16(0xdd)), payload: []byte{0xaf, 0x0d, 0x5a}, }, }, } for _, cur := range tt { parsed := &H265FragmentationUnitPacket{} if cur.WithDONL { parsed.WithDONL(cur.WithDONL) } // Just for code coverage sake parsed.isH265Packet() _, err := parsed.Unmarshal(cur.Raw) if cur.ExpectedErr != nil { assert.ErrorIs(t, err, cur.ExpectedErr) } else { assert.NoError(t, err) } if cur.ExpectedFU == nil { continue } assert.Equal(t, cur.ExpectedFU.PayloadHeader(), parsed.PayloadHeader()) assert.Equal(t, cur.ExpectedFU.FuHeader(), parsed.FuHeader()) if cur.ExpectedFU.DONL() != nil { assert.Equal(t, *cur.ExpectedFU.DONL(), *parsed.DONL()) } else { assert.Nil(t, parsed.DONL()) } assert.Equal(t, cur.ExpectedFU.Payload(), parsed.Payload()) } } func TestH265_TemporalScalabilityControlInformation(t *testing.T) { tt := [...]struct { Value H265TSCI ExpectedTL0PICIDX uint8 ExpectedIrapPicID uint8 ExpectedS bool ExpectedE bool ExpectedRES uint8 }{ {}, { Value: H265TSCI((uint32(0xCA) << 24) | (uint32(0xFE) << 16)), ExpectedTL0PICIDX: 0xCA, ExpectedIrapPicID: 0xFE, }, { Value: H265TSCI(uint32(1) << 15), ExpectedS: true, }, { Value: H265TSCI(uint32(1) << 14), ExpectedE: true, }, { Value: H265TSCI(uint32(0x0A) << 8), ExpectedRES: 0x0A, }, // Sets RES, and force sets S and E to 0. { Value: H265TSCI((uint32(0xAA) << 8) & ^(uint32(1) << 15) & ^(uint32(1) << 14)), ExpectedRES: 0xAA & 0b00111111, }, } for _, cur := range tt { assert.Equal(t, cur.ExpectedTL0PICIDX, cur.Value.TL0PICIDX()) assert.Equal(t, cur.ExpectedIrapPicID, cur.Value.IrapPicID()) assert.Equal(t, cur.ExpectedS, cur.Value.S()) assert.Equal(t, cur.ExpectedE, cur.Value.E()) assert.Equal(t, cur.ExpectedRES, cur.Value.RES()) } } func TestH265_PACI_Packet(t *testing.T) { tt := [...]struct { Raw []byte ExpectedFU *H265PACIPacket ExpectedErr error }{ { Raw: nil, ExpectedErr: errNilPacket, }, { Raw: []byte{}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93}, ExpectedErr: errShortPacket, }, // FBit enabled in H265NALUHeader { Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Type not '50' in H265NALUHeader { Raw: []byte{0x40, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errInvalidH265PacketType, }, // Invalid header extension size { Raw: []byte{0x64, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errShortPacket, }, // No Header Extension { Raw: []byte{0x64, 0x01, 0x64, 0x00, 0xab, 0xcd, 0xef}, ExpectedFU: &H265PACIPacket{ payloadHeader: newH265NALUHeader(0x64, 0x01), paciHeaderFields: (uint16(0x64) << 8) | uint16(0x00), phes: nil, payload: []byte{0xab, 0xcd, 0xef}, }, }, // Header Extension 1 byte { Raw: []byte{0x64, 0x01, 0x64, 0x10, 0xff, 0xab, 0xcd, 0xef}, ExpectedFU: &H265PACIPacket{ payloadHeader: newH265NALUHeader(0x64, 0x01), paciHeaderFields: (uint16(0x64) << 8) | uint16(0x10), phes: []byte{0xff}, payload: []byte{0xab, 0xcd, 0xef}, }, }, // Header Extension TSCI { Raw: []byte{0x64, 0x01, 0x64, 0b00111000, 0xaa, 0xbb, 0x80, 0xab, 0xcd, 0xef}, ExpectedFU: &H265PACIPacket{ payloadHeader: newH265NALUHeader(0x64, 0x01), paciHeaderFields: (uint16(0x64) << 8) | uint16(0b00111000), phes: []byte{0xaa, 0xbb, 0x80}, payload: []byte{0xab, 0xcd, 0xef}, }, }, } for _, cur := range tt { parsed := &H265PACIPacket{} _, err := parsed.Unmarshal(cur.Raw) // Just for code coverage sake parsed.isH265Packet() if cur.ExpectedErr != nil { assert.ErrorIs(t, err, cur.ExpectedErr) } else { assert.NoError(t, err) } if cur.ExpectedFU == nil { continue } assert.Equal(t, cur.ExpectedFU.PayloadHeader(), parsed.PayloadHeader()) assert.Equal(t, cur.ExpectedFU.A(), parsed.A()) assert.Equal(t, cur.ExpectedFU.CType(), parsed.CType()) assert.Equal(t, cur.ExpectedFU.PHSsize(), parsed.PHSsize()) assert.Equal(t, cur.ExpectedFU.F0(), parsed.F0()) assert.Equal(t, cur.ExpectedFU.F1(), parsed.F1()) assert.Equal(t, cur.ExpectedFU.F2(), parsed.F2()) assert.Equal(t, cur.ExpectedFU.Y(), parsed.Y()) assert.Equal(t, cur.ExpectedFU.PHES(), parsed.PHES()) assert.Equal(t, cur.ExpectedFU.Payload(), parsed.Payload()) if cur.ExpectedFU.TSCI() != nil { assert.Equal(t, cur.ExpectedFU.TSCI(), parsed.TSCI()) } else { assert.Nil(t, parsed.TSCI()) } } } func TestH265_Packet(t *testing.T) { tt := [...]struct { Raw []byte WithDONL bool ExpectedPacketType any ExpectedErr error }{ { Raw: nil, ExpectedErr: errNilPacket, }, { Raw: []byte{}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x64, 0x01, 0x93, 0xaf}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x01, 0x01}, WithDONL: true, ExpectedErr: errShortPacket, }, // FBit enabled in H265NALUHeader { Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Valid H265SingleNALUnitPacket { Raw: []byte{0x01, 0x01, 0xab, 0xcd, 0xef}, ExpectedPacketType: &H265SingleNALUnitPacket{}, }, // Invalid H265SingleNALUnitPacket { Raw: []byte{0x01, 0x01, 0x93, 0xaf}, ExpectedErr: errShortPacket, WithDONL: true, }, // Valid H265PACIPacket { Raw: []byte{0x64, 0x01, 0x64, 0b00111000, 0xaa, 0xbb, 0x80, 0xab, 0xcd, 0xef}, ExpectedPacketType: &H265PACIPacket{}, }, // Valid H265FragmentationUnitPacket { Raw: []byte{0x62, 0x01, 0x93, 0xcc, 0xdd, 0xaf, 0x0d, 0x5a}, ExpectedPacketType: &H265FragmentationPacket{}, WithDONL: true, }, // Valid H265AggregationPacket { Raw: []byte{0x60, 0x01, 0xcc, 0xdd, 0x00, 0x02, 0xff, 0xee, 0x77, 0x00, 0x01, 0xaa}, ExpectedPacketType: &H265AggregationPacket{}, WithDONL: true, }, // Invalid H265AggregationPacket { Raw: []byte{0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00}, ExpectedErr: errShortPacket, WithDONL: true, }, } for _, cur := range tt { pck := &H265Packet{} if cur.WithDONL { pck.WithDONL(true) } _, err := pck.Unmarshal(cur.Raw) if cur.ExpectedErr == nil { assert.NoError(t, err) } else { assert.ErrorIs(t, err, cur.ExpectedErr) } if cur.ExpectedErr != nil { continue } assert.IsType(t, cur.ExpectedPacketType, pck.Packet()) } } func TestH265IsPartitionHead(t *testing.T) { h265 := H265Packet{} assert.False(t, h265.IsPartitionHead(nil), "nil must not be a partition head") assert.False(t, h265.IsPartitionHead([]byte{}), "empty nalu must not be a partition head") singleNalu := []byte{0x01, 0x01, 0xab, 0xcd, 0xef} assert.True(t, h265.IsPartitionHead(singleNalu), "single nalu must be a partition head") fbitNalu := []byte{0x80, 0x00, 0x00} assert.True(t, h265.IsPartitionHead(fbitNalu), "fbit nalu must be a partition head") fuStartNalu := []byte{0x62, 0x01, 0x93} assert.True(t, h265.IsPartitionHead(fuStartNalu), "fu start nalu must be a partition head") fuEndNalu := []byte{0x62, 0x01, 0x53} assert.False(t, h265.IsPartitionHead(fuEndNalu), "fu end nalu must not be a partition head") } func TestH265_Packet_Real(t *testing.T) { // Tests decoding of real H265 payloads extracted from a Wireshark dump. // nolint: lll tt := [...]string{ "\x40\x01\x0c\x01\xff\xff\x01\x60\x00\x00\x03\x00\xb0\x00\x00\x03\x00\x00\x03\x00\x7b\xac\x09", "\x42\x01\x01\x01\x60\x00\x00\x03\x00\xb0\x00\x00\x03\x00\x00\x03\x00\x7b\xa0\x03\xc0\x80\x10\xe5\x8d\xae\x49\x32\xf4\xdc\x04\x04\x04\x02", "\x44\x01\xc0\xf2\xf0\x3c\x90", "\x4e\x01\xe5\x04\x61\x0c\x00\x00\x80", "\x62\x01\x93\xaf\x0d\x5a\xfe\x67\x77\x29\xc0\x74\xf3\x57\x4c\x16\x94\xaa\x7c\x2a\x64\x5f\xe9\xa5\xb7\x2a\xa3\x95\x9d\x94\xa7\xb4\xd3\xc4\x4a\xb1\xb7\x69\xca\xbe\x75\xc5\x64\xa8\x97\x4b\x8a\xbf\x7e\xf0\x0f\xc3\x22\x60\x67\xab\xae\x96\xd6\x99\xca\x7a\x8d\x35\x93\x1a\x67\x60\xe7\xbe\x7e\x13\x95\x3c\xe0\x11\xc1\xc1\xa7\x48\xef\xf7\x7b\xb0\xeb\x35\x49\x81\x4e\x4e\x54\xf7\x31\x6a\x38\xa1\xa7\x0c\xd6\xbe\x3b\x25\xba\x08\x19\x0b\x49\xfd\x90\xbb\x73\x7a\x45\x8c\xb9\x73\x43\x04\xc5\x5f\xda\x0f\xd5\x70\x4c\x11\xee\x72\xb8\x6a\xb4\x95\x62\x64\xb6\x23\x14\x7e\xdb\x0e\xa5\x0f\x86\x31\xe4\xd1\x64\x56\x43\xf6\xb7\xe7\x1b\x93\x4a\xeb\xd0\xa6\xe3\x1f\xce\xda\x15\x67\x05\xb6\x77\x36\x8b\x27\x5b\xc6\xf2\x95\xb8\x2b\xcc\x9b\x0a\x03\x05\xbe\xc3\xd3\x85\xf5\x69\xb6\x19\x1f\x63\x2d\x8b\x65\x9e\xc3\x9d\xd2\x44\xb3\x7c\x86\x3b\xea\xa8\x5d\x02\xe5\x40\x03\x20\x76\x48\xff\xf6\x2b\x0d\x18\xd6\x4d\x49\x70\x1a\x5e\xb2\x89\xca\xec\x71\x41\x79\x4e\x94\x17\x0c\x57\x51\x55\x14\x61\x40\x46\x4b\x3e\x17\xb2\xc8\xbd\x1c\x06\x13\x91\x72\xf8\xc8\xfc\x6f\xb0\x30\x9a\xec\x3b\xa6\xc9\x33\x0b\xa5\xe5\xf4\x65\x7a\x29\x8b\x76\x62\x81\x12\xaf\x20\x4c\xd9\x21\x23\x9e\xeb\xc9\x0e\x5b\x29\x35\x7f\x41\xcd\xce\xa1\xc4\xbe\x01\x30\xb9\x11\xc3\xb1\xe4\xce\x45\xd2\x5c\xb3\x1e\x69\x78\xba\xb1\x72\xe4\x88\x54\xd8\x5d\xd0\xa8\x3a\x74\xad\xe5\xc7\xc1\x59\x7c\x78\x15\x26\x37\x3d\x50\xae\xb3\xa4\x5b\x6c\x7d\x65\x66\x85\x4d\x16\x9a\x67\x74\xad\x55\x32\x3a\x84\x85\x0b\x6a\xeb\x24\x97\xb4\x20\x4d\xca\x41\x61\x7a\xd1\x7b\x60\xdb\x7f\xd5\x61\x22\xcf\xd1\x7e\x4c\xf3\x85\xfd\x13\x63\xe4\x9d\xed\xac\x13\x0a\xa0\x92\xb7\x34\xde\x65\x0f\xd9\x0f\x9b\xac\xe2\x47\xe8\x5c\xb3\x11\x8e\xc6\x08\x19\xd0\xb0\x85\x52\xc8\x5c\x1b\x08\x0a\xce\xc9\x6b\xa7\xef\x95\x2f\xd0\xb8\x63\xe5\x4c\xd4\xed\x6e\x87\xe9\xd4\x0a\xe6\x11\x44\x63\x00\x94\x18\xe9\x28\xba\xcf\x92\x43\x06\x59\xdd\x37\x4f\xd3\xef\x9d\x31\x5e\x9b\x48\xf9\x1f\x3e\x7b\x95\x3a\xbd\x1f\x71\x55\x0c\x06\xf9\x86\xf8\x3d\x39\x16\x50\xb3\x21\x11\x19\x6f\x70\xa9\x48\xe8\xbb\x0a\x11\x23\xf8\xab\xfe\x44\xe0\xbb\xe8\x64\xfa\x85\xe4\x02\x55\x88\x41\xc6\x30\x7f\x10\xad\x75\x02\x4b\xef\xe1\x0b\x06\x3c\x10\x49\x83\xf9\xd1\x3e\x3e\x67\x86\x4c\xf8\x9d\xde\x5a\xc4\xc8\xcf\xb6\xf4\xb0\xd3\x34\x58\xd4\x7b\x4d\xd3\x37\x63\xb2\x48\x8a\x7e\x20\x00\xde\xb4\x42\x8f\xda\xe9\x43\x9e\x0c\x16\xce\x79\xac\x2c\x70\xc1\x89\x05\x36\x62\x6e\xd9\xbc\xfb\x63\xc6\x79\x89\x3c\x90\x89\x2b\xd1\x8c\xe0\xc2\x54\xc7\xd6\xb4\xe8\x9e\x96\x55\x6e\x7b\xd5\x7f\xac\xd4\xa7\x1c\xa0\xdf\x01\x30\xad\xc0\x9f\x69\x06\x10\x43\x7f\xf4\x5d\x62\xa3\xea\x73\xf2\x14\x79\x19\x13\xea\x59\x14\x79\xa8\xe7\xce\xce\x44\x25\x13\x41\x18\x57\xdd\xce\xe4\xbe\xcc\x20\x80\x29\x71\x73\xa7\x7c\x86\x39\x76\xf4\xa7\x1c\x63\x24\x21\x93\x1e\xb5\x9a\x5c\x8a\x9e\xda\x8b\x9d\x88\x97\xfc\x98\x7d\x26\x74\x04\x1f\xa8\x10\x4f\x45\xcd\x46\xe8\x28\xe4\x8e\x59\x67\x63\x4a\xcf\x1e\xed\xdd\xbb\x79\x2f\x8d\x94\xab\xfc\xdb\xc5\x79\x1a\x4d\xcd\x53\x41\xdf\xd1\x7a\x8f\x46\x3e\x1f\x79\x88\xe3\xee\x9f\xc4\xc1\xe6\x2e\x89\x4d\x28\xc9\xca\x28\xc2\x0a\xc5\xc7\xf1\x22\xcd\xb3\x36\xfa\xe3\x7e\xa6\xcd\x95\x55\x5e\x0e\x1a\x75\x7f\x65\x27\xd3\x37\x4f\x23\xc5\xab\x49\x68\x4e\x02\xb5\xbf\xd7\x95\xc0\x78\x67\xbc\x1a\xe9\xae\x6f\x44\x58\x8a\xc2\xce\x42\x98\x4e\x77\xc7\x2a\xa0\xa7\x7d\xe4\x3b\xd1\x20\x82\x1a\xd3\xe2\xc7\x76\x5d\x06\x46\xb5\x24\xd7\xfb\x57\x63\x2b\x19\x51\x48\x65\x6d\xfb\xe0\x98\xd1\x14\x0e\x17\x64\x29\x34\x6f\x6e\x66\x9e\x8d\xc9\x89\x49\x69\xee\x74\xf3\x35\xe6\x8b\x67\x56\x95\x7f\x1b\xe9\xed\x8c\x0f\xe2\x19\x59\xbf\x03\x35\x55\x3c\x04\xbc\x40\x52\x90\x10\x08\xad\xa7\x65\xe0\x31\xcb\xcf\x3d\xd4\x62\x68\x01\x0d\xed\xf5\x28\x64\x2d\xaa\x7c\x99\x15\x8d\x70\x32\x53\xb8\x9d\x0a\x3c\xbf\x91\x02\x04\xd0\xee\x87\xce\x04\xcc\x3e\xa8\x20\xfd\x97\xdf\xbf\x4a\xbc\xfc\xc9\x7c\x77\x21\xcc\x23\x6f\x59\x38\xd8\xd9\xa0\x0e\xb1\x23\x4e\x04\x3f\x14\x9e\xcc\x05\x54\xab\x20\x69\xed\xa4\xd5\x1d\xb4\x1b\x52\xed\x6a\xea\xeb\x7f\xd1\xbc\xfd\x75\x20\xa0\x1c\x59\x8c\x5a\xa1\x2a\x70\x64\x11\xb1\x7b\xc1\x24\x80\x28\x51\x4c\x94\xa1\x95\x64\x72\xe8\x90\x67\x38\x74\x2b\xab\x38\x46\x12\x71\xce\x19\x98\x98\xf7\x89\xd4\xfe\x2f\x2a\xc5\x61\x20\xd0\xa4\x1a\x51\x3c\x82\xc8\x18\x31\x7a\x10\xe8\x1c\xc6\x95\x5a\xa0\x82\x88\xce\x8f\x4b\x47\x85\x7e\x89\x95\x95\x52\x1e\xac\xce\x45\x57\x61\x38\x97\x2b\x62\xa5\x14\x6f\xc3\xaa\x6c\x35\x83\xc9\xa3\x1e\x30\x89\xf4\xb1\xea\x4f\x39\xde\xde\xc7\x46\x5c\x0e\x85\x41\xec\x6a\xa4\xcb\xee\x70\x9c\x57\xd9\xf4\xa1\xc3\x9c\x2a\x0a\xf0\x5d\x58\xb0\xae\xd4\xdc\xc5\x6a\xa8\x34\xfa\x23\xef\xef\x08\x39\xc3\x3d\xea\x11\x6e\x6a\xe0\x1e\xd0\x52\xa8\xc3\x6e\xc9\x1c\xfc\xd0\x0c\x4c\xea\x0d\x82\xcb\xdd\x29\x1a\xc4\x4f\x6e\xa3\x4d\xcb\x7a\x38\x77\xe5\x15\x6e\xad\xfa\x9d\x2f\x02\xb6\x39\x84\x3a\x60\x8f\x71\x9f\x92\xe5\x24\x4f\xbd\x18\x49\xd5\xef\xbf\x70\xfb\xd1\x4c\x2e\xfc\x2f\x36\xf3\x00\x31\x2e\x90\x18\xcc\xf4\x71\xb9\xe4\xf9\xbe\xcb\x5e\xff\xf3\xe7\xf8\xca\x03\x60\x66\xb3\xc9\x5a\xf9\x74\x09\x02\x57\xb6\x90\x94\xfc\x41\x35\xdc\x35\x3f\x32\x7a\xa6\xa5\xcd\x8a\x8f\xc8\x3d\xc8\x81\xc3\xec\x37\x74\x86\x61\x41\x0d\xc5\xe2\xc8\x0c\x84\x2b\x3b\x71\x58\xde\x1b\xe3\x20\x65\x2e\x76\xf4\x98\xd8\xaa\x78\xe6\xeb\xb8\x85\x0d\xa0\xd0\xf5\x57\x64\x01\x58\x55\x82\xd5\x0f\x2d\x9c\x3e\x2a\xa0\x7e\xaf\x42\xf3\x37\xd1\xb3\xaf\xda\x5b\xa9\xda\xe3\x89\x5d\xf1\xca\xa5\x12\x3d\xe7\x91\x95\x53\x21\x72\xca\x7f\xf6\x79\x59\x21\xcf\x30\x18\xfb\x78\x55\x40\x59\xc3\xf9\xf1\xdd\x58\x44\x5e\x83\x11\x5c\x2d\x1d\x91\xf6\x01\x3d\x3f\xd4\x33\x81\x66\x6c\x40\x7a\x9d\x70\x10\x58\xe6\x53\xad\x85\x11\x99\x3e\x4b\xbc\x31\xc6\x78\x9d\x79\xc5\xde\x9f\x2e\x43\xfa\x76\x84\x2f\xfd\x28\x75\x12\x48\x25\xfd\x15\x8c\x29\x6a\x91\xa4\x63\xc0\xa2\x8c\x41\x3c\xf1\xb0\xf8\xdf\x66\xeb\xbd\x14\x88\xa9\x81\xa7\x35\xc4\x41\x40\x6c\x10\x3f\x09\xbd\xb5\xd3\x7a\xee\x4b\xd5\x86\xff\x36\x03\x6b\x78\xde", "\x62\x01\x53\x8a\xe9\x25\xe1\x06\x09\x8e\xba\x12\x74\x87\x09\x9a\x95\xe4\x86\x62\x2b\x4b\xf9\xa6\x2e\x7b\x35\x43\xf7\x39\x99\x0f\x3b\x6f\xfd\x1a\x6e\x23\x54\x70\xb5\x1d\x10\x1c\x63\x40\x96\x99\x41\xb6\x96\x0b\x70\x98\xec\x17\xb0\xaa\xdc\x4a\xab\xe8\x3b\xb7\x6b\x00\x1c\x5b\xc3\xe0\xa2\x8b\x7c\x17\xc8\x92\xc9\xb0\x92\xb6\x70\x84\x95\x30", "\x4e\x01\xe5\x04\x35\xac\x00\x00\x80", "\x62\x01\x41\xb0\x75\x5c\x27\x46\xef\x8a\xe7\x1d\x50\x38\xb2\x13\x33\xe0\x79\x35\x1b\xc2\xb5\x79\x73\xe7\xc2\x6f\xb9\x1a\x8c\x21\x0e\xa9\x54\x17\x6c\x41\xab\xc8\x16\x57\xec\x5e\xeb\x89\x3b\xa9\x90\x8c\xff\x4d\x46\x8b\xf0\xd9\xc0\xd0\x51\xcf\x8b\x88\xf1\x5f\x1e\x9e\xc1\xb9\x1f\xe3\x06\x45\x35\x8a\x47\xe8\x9a\xf2\x4f\x19\x4c\xf8\xce\x68\x1b\x63\x34\x11\x75\xea\xe5\xb1\x0f\x38\xcc\x05\x09\x8b\x3e\x2b\x88\x84\x9d\xc5\x03\xc3\xc0\x90\x32\xe2\x45\x69\xb1\xe5\xf7\x68\x6b\x16\x90\xa0\x40\xe6\x18\x74\xd8\x68\xf3\x34\x38\x99\xf2\x6c\xb7\x1a\x35\x21\xca\x52\x56\x4c\x7f\xb2\xa3\xd5\xb8\x40\x50\x48\x3e\xdc\xdf\x0b\xf5\x54\x5a\x15\x1a\xe2\xc3\xb4\x94\xda\x3f\xb5\x34\xa2\xca\xbc\x2f\xe0\xa4\xe5\x69\xf4\xbf\x62\x4d\x15\x21\x1b\x11\xfc\x39\xaa\x86\x74\x96\x63\xfd\x07\x53\x26\xf6\x34\x72\xeb\x14\x37\x98\x0d\xf4\x68\x91\x2c\x6b\x46\x83\x88\x82\x04\x8b\x9f\xb8\x32\x73\x75\x8b\xf9\xac\x71\x42\xd1\x2d\xb4\x28\x28\xf5\x78\xe0\x32\xf3\xe1\xfc\x43\x6b\xf9\x92\xf7\x48\xfe\x7f\xc0\x17\xbd\xfd\xba\x2f\x58\x6f\xee\x84\x03\x18\xce\xb0\x9d\x8d\xeb\x22\xf1\xfc\xb1\xcf\xff\x2f\xb2\x9f\x6c\xe5\xb4\x69\xdc\xdd\x20\x93\x00\x30\xad\x56\x04\x66\x7e\xa3\x3c\x18\x4b\x43\x66\x00\x27\x1e\x1c\x09\x11\xd8\xf4\x8a\x9e\xc5\x6a\x94\xe5\xae\x0b\x8a\xbe\x84\xda\xe5\x44\x7f\x38\x1c\xe7\xbb\x03\x19\x66\xe1\x5d\x1d\xc1\xbd\x3d\xc6\xb7\xe3\xff\x7f\x8e\xff\x1e\xf6\x9e\x6f\x58\x27\x74\x65\xef\x02\x5d\xa4\xde\x27\x7f\x51\xe3\x4b\x9e\x3f\x79\x83\xbd\x1b\x8f\x0d\x77\xfb\xbc\xc5\x9f\x15\xa7\x4e\x05\x8a\x24\x97\x66\xb2\x7c\xf6\xe1\x84\x54\xdb\x39\x5e\xf6\x1b\x8f\x05\x73\x1d\xb6\x8e\xd7\x09\x9a\xc5\x92\x80", } for _, cur := range tt { pck := &H265Packet{} _, err := pck.Unmarshal([]byte(cur)) assert.True(t, err == nil || errors.Is(err, errExpectFragmentationStartUnit)) } } func TestH265Payloader_Payload(t *testing.T) { tt := []struct { Name string Data []byte MTU uint16 AddDONL bool SkipAggregation bool ExpectedLen int ExpectedData *[][]byte Msg string }{ { Name: "Positive MTU, nil payload", MTU: 1, Data: nil, ExpectedLen: 0, Msg: "Generated payload must be empty", }, { Name: "Positive MTU, empty NAL", MTU: 1, Data: []byte{}, ExpectedLen: 0, Msg: "Generated payload should be empty", }, { Name: "Zero MTU, start code", MTU: 0, Data: []byte{0x00, 0x00, 0x01}, ExpectedLen: 0, Msg: "Generated payload should be empty", }, { Name: "Positive MTU, 1 byte payload", MTU: 1, Data: []byte{0x90}, ExpectedLen: 0, Msg: "Generated payload should be empty. H.265 nal unit too small", }, { Name: "MTU:1, 2 byte payload", MTU: 1, Data: []byte{0x46, 0x01}, ExpectedLen: 0, Msg: "Generated payload should be empty. H.265 nal unit too small", }, { Name: "MTU:2, 2 byte payload.", MTU: 2, Data: []byte{0x46, 0x01}, ExpectedLen: 0, Msg: "Generated payload should be empty. min MTU is 4", }, { Name: "MTU:4, 2 byte payload.", MTU: 4, Data: []byte{0x46, 0x01}, ExpectedData: &[][]byte{{0x46, 0x01}}, Msg: "AUD packetization failed", }, { Name: "Negative MTU, small payload", MTU: 0, Data: []byte{0x90, 0x90, 0x90}, ExpectedLen: 0, }, { Name: "Negative MTU, small payload", MTU: 0, Data: []byte{0x90, 0x90, 0x90}, ExpectedLen: 0, }, { Name: "Negative MTU, small payload", MTU: 1, Data: []byte{0x90, 0x90, 0x90}, ExpectedLen: 0, }, { Name: "Negative MTU, small payload", MTU: 5, Data: []byte{0x90, 0x90, 0x90}, ExpectedData: &[][]byte{{0x90, 0x90, 0x90}}, }, { Name: "Large payload", MTU: 5, Data: []byte{ 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, }, ExpectedData: &[][]byte{ {0x62, 0x01, 0x80, 0x02, 0x03}, {0x62, 0x01, 0x00, 0x04, 0x05}, {0x62, 0x01, 0x00, 0x06, 0x07}, {0x62, 0x01, 0x00, 0x08, 0x09}, {0x62, 0x01, 0x00, 0x10, 0x11}, {0x62, 0x01, 0x00, 0x12, 0x13}, {0x62, 0x01, 0x40, 0x14, 0x15}, }, Msg: "Large payload split across fragmentation Packets", }, { Name: "Short MTU, multiple NALUs flushed in single packet", MTU: 5, Data: []byte{ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03, }, ExpectedData: &[][]byte{{0x00, 0x01}, {0x02, 0x03}}, Msg: "multiple Single NALUs packetization should succeed", }, { Name: "Enough MUT, multiple NALUs create Signle Packet", MTU: 10, Data: []byte{ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03, }, ExpectedData: &[][]byte{{0x60, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x02, 0x03}}, Msg: "Aggregation packetization should succeed", }, { Name: "Enough MUT, multiple NALUs flushed two Packets, don't aggregate", MTU: 5, Data: []byte{ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x01, 0x04, 0x05, }, ExpectedData: &[][]byte{{0x00, 0x01}, {0x02, 0x03}, {0x04, 0x05}}, Msg: "multiple Single NALUs packetization should succeed", }, { Name: "Enough MUT, multiple NALUs flushed two Packets, aggregate", MTU: 15, Data: []byte{ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x01, 0x04, 0x05, }, ExpectedData: &[][]byte{{0x60, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x02, 0x03, 0x00, 0x02, 0x04, 0x05}}, Msg: "Aggregation packetization should succeed", }, // Add DONL = true { Name: "DONL, invalid MTU:1", MTU: 1, Data: []byte{0x01}, AddDONL: true, ExpectedLen: 0, Msg: "Generated payload must be empty", }, { Name: "DONL MTU:4, 2 byte payload.", MTU: 4, Data: []byte{0x00, 0x01}, AddDONL: true, ExpectedLen: 0, Msg: "Generated payload must be empty", }, { Name: "DONL single NALU minimum payload", MTU: 6, Data: []byte{0x00, 0x01}, AddDONL: true, ExpectedData: &[][]byte{{0x00, 0x01, 0x00, 0x00}}, Msg: "single NALU should be packetized", }, { Name: "DONL multiple NALU", MTU: 6, Data: []byte{ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x01, 0x04, 0x05, }, AddDONL: true, ExpectedData: &[][]byte{ {0x00, 0x01, 0x00, 0x00}, {0x02, 0x03, 0x00, 0x01}, {0x04, 0x05, 0x00, 0x02}, }, Msg: "DONL should be incremented", }, { Name: "DONL aggregation minimum payload", MTU: 18, Data: []byte{ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x01, 0x04, 0x05, }, AddDONL: true, ExpectedData: &[][]byte{ { 0x60, 0x01, // NALU Header + Layer ID + TID 0x00, 0x00, // DONL 0x00, 0x02, 0x00, 0x01, 0x00, // DONL 0x00, 0x02, 0x02, 0x03, 0x01, // DONL 0x00, 0x02, 0x04, 0x05, }, }, Msg: "DONL Aggregation packetization should succeed", }, { Name: "DONL Large payload", MTU: 7, Data: []byte{ 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, }, AddDONL: true, ExpectedData: &[][]byte{ {0x62, 0x01, 0x80, 0x00, 0x00, 0x02, 0x03}, {0x62, 0x01, 0x00, 0x00, 0x01, 0x04, 0x05}, {0x62, 0x01, 0x00, 0x00, 0x02, 0x06, 0x07}, {0x62, 0x01, 0x00, 0x00, 0x03, 0x08, 0x09}, {0x62, 0x01, 0x00, 0x00, 0x04, 0x10, 0x11}, {0x62, 0x01, 0x00, 0x00, 0x05, 0x12, 0x13}, {0x62, 0x01, 0x40, 0x00, 0x06, 0x14, 0x15}, }, Msg: "DONL Large payload split across fragmentation Packets", }, // SkipAggregation = true { Name: "SkipAggregation Enough MUT, multiple NALUs", MTU: 4, Data: []byte{ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x01, 0x04, 0x05, }, SkipAggregation: true, ExpectedData: &[][]byte{{0x00, 0x01}, {0x02, 0x03}, {0x04, 0x05}}, Msg: "Aggregation packetization should be skipped", }, } for _, cur := range tt { t.Run(cur.Name, func(t *testing.T) { pck := H265Payloader{AddDONL: cur.AddDONL, SkipAggregation: cur.SkipAggregation} res := pck.Payload(cur.MTU, cur.Data) if cur.ExpectedData != nil { assert.Equal(t, *cur.ExpectedData, res) } else { assert.Len(t, res, cur.ExpectedLen) } }) } } func TestH265Payloader_Real(t *testing.T) { // curl -LO "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h265/1080/Big_Buck_Bunny_1080_10s_1MB.mp4" // ffmpeg -i Big_Buck_Bunny_1080_10s_1MB.mp4 -c:v copy Big_Buck_Bunny_1080_10s_1MB.h265 // hexdump -v -e '1/1 "0x%02x, "' Big_Buck_Bunny_1080_10s_1MB.h265 > aaa // nolint: lll payload := []byte{ 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0x95, 0x98, 0x09, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x03, 0xc0, 0x80, 0x10, 0xe5, 0x96, 0x56, 0x69, 0x24, 0xca, 0xf0, 0x10, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x01, 0xe0, 0x80, 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40, 0x00, 0x00, 0x00, 0x01, 0x4e, 0x01, 0x05, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x71, 0x2c, 0xa2, 0xde, 0x09, 0xb5, 0x17, 0x47, 0xdb, 0xbb, 0x55, 0xa4, 0xfe, 0x7f, 0xc2, 0xfc, 0x4e, 0x78, 0x32, 0x36, 0x35, 0x20, 0x28, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x31, 0x35, 0x31, 0x29, 0x20, 0x2d, 0x20, 0x32, 0x2e, 0x36, 0x2b, 0x34, 0x39, 0x2d, 0x37, 0x32, 0x31, 0x39, 0x33, 0x37, 0x36, 0x64, 0x65, 0x34, 0x32, 0x61, 0x3a, 0x5b, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x5d, 0x5b, 0x47, 0x43, 0x43, 0x20, 0x37, 0x2e, 0x33, 0x2e, 0x30, 0x5d, 0x5b, 0x36, 0x34, 0x20, 0x62, 0x69, 0x74, 0x5d, 0x20, 0x38, 0x62, 0x69, 0x74, 0x2b, 0x31, 0x30, 0x62, 0x69, 0x74, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x35, 0x2f, 0x48, 0x45, 0x56, 0x43, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x32, 0x30, 0x31, 0x33, 0x2d, 0x32, 0x30, 0x31, 0x38, 0x20, 0x28, 0x63, 0x29, 0x20, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x6f, 0x72, 0x65, 0x77, 0x61, 0x72, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x78, 0x32, 0x36, 0x35, 0x2e, 0x6f, 0x72, 0x67, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0x63, 0x70, 0x75, 0x69, 0x64, 0x3d, 0x31, 0x30, 0x35, 0x30, 0x31, 0x31, 0x31, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x2d, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x33, 0x20, 0x6e, 0x75, 0x6d, 0x61, 0x2d, 0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x3d, 0x38, 0x20, 0x77, 0x70, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x6d, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x70, 0x73, 0x6e, 0x72, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x73, 0x69, 0x6d, 0x20, 0x6c, 0x6f, 0x67, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x32, 0x20, 0x62, 0x69, 0x74, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x38, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x63, 0x73, 0x70, 0x3d, 0x31, 0x20, 0x66, 0x70, 0x73, 0x3d, 0x33, 0x30, 0x2f, 0x31, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x72, 0x65, 0x73, 0x3d, 0x31, 0x39, 0x32, 0x30, 0x78, 0x31, 0x30, 0x38, 0x30, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x61, 0x63, 0x65, 0x3d, 0x30, 0x20, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x2d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x30, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x69, 0x64, 0x63, 0x3d, 0x30, 0x20, 0x68, 0x69, 0x67, 0x68, 0x2d, 0x74, 0x69, 0x65, 0x72, 0x3d, 0x31, 0x20, 0x75, 0x68, 0x64, 0x2d, 0x62, 0x64, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x3d, 0x34, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x2d, 0x6e, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x20, 0x61, 0x6e, 0x6e, 0x65, 0x78, 0x62, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x75, 0x64, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x72, 0x64, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x68, 0x61, 0x73, 0x68, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2d, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x70, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x32, 0x35, 0x30, 0x20, 0x67, 0x6f, 0x70, 0x2d, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x30, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x34, 0x20, 0x62, 0x2d, 0x61, 0x64, 0x61, 0x70, 0x74, 0x3d, 0x32, 0x20, 0x62, 0x2d, 0x70, 0x79, 0x72, 0x61, 0x6d, 0x69, 0x64, 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x2d, 0x62, 0x69, 0x61, 0x73, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x2d, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x32, 0x35, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x2d, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x3d, 0x34, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d, 0x34, 0x30, 0x20, 0x72, 0x61, 0x64, 0x6c, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x20, 0x63, 0x74, 0x75, 0x3d, 0x36, 0x34, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x63, 0x75, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x38, 0x20, 0x72, 0x65, 0x63, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6d, 0x70, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x74, 0x75, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x33, 0x32, 0x20, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x2d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x31, 0x20, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x64, 0x65, 0x70, 0x74, 0x68, 0x3d, 0x31, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x74, 0x75, 0x3d, 0x30, 0x20, 0x72, 0x64, 0x6f, 0x71, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x32, 0x20, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x2d, 0x72, 0x64, 0x3d, 0x30, 0x2e, 0x30, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x73, 0x69, 0x6d, 0x2d, 0x72, 0x64, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x68, 0x69, 0x64, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x6e, 0x72, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x6e, 0x72, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x65, 0x64, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x2d, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x3d, 0x33, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x72, 0x65, 0x66, 0x73, 0x3d, 0x33, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x6d, 0x65, 0x3d, 0x33, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, 0x3d, 0x33, 0x20, 0x6d, 0x65, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x35, 0x37, 0x20, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x2d, 0x6d, 0x76, 0x70, 0x20, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x62, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x7a, 0x65, 0x2d, 0x73, 0x72, 0x63, 0x2d, 0x70, 0x69, 0x63, 0x73, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d, 0x30, 0x3a, 0x30, 0x20, 0x73, 0x61, 0x6f, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x61, 0x6f, 0x2d, 0x6e, 0x6f, 0x6e, 0x2d, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x72, 0x64, 0x3d, 0x34, 0x20, 0x6e, 0x6f, 0x2d, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x2d, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x72, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x66, 0x61, 0x73, 0x74, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x6e, 0x6f, 0x2d, 0x74, 0x73, 0x6b, 0x69, 0x70, 0x2d, 0x66, 0x61, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x75, 0x2d, 0x6c, 0x6f, 0x73, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x62, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x72, 0x64, 0x2d, 0x73, 0x6b, 0x69, 0x70, 0x20, 0x72, 0x64, 0x70, 0x65, 0x6e, 0x61, 0x6c, 0x74, 0x79, 0x3d, 0x30, 0x20, 0x70, 0x73, 0x79, 0x2d, 0x72, 0x64, 0x3d, 0x32, 0x2e, 0x30, 0x30, 0x20, 0x70, 0x73, 0x79, 0x2d, 0x72, 0x64, 0x6f, 0x71, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x64, 0x2d, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x6f, 0x73, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x63, 0x62, 0x71, 0x70, 0x6f, 0x66, 0x66, 0x73, 0x3d, 0x30, 0x20, 0x63, 0x72, 0x71, 0x70, 0x6f, 0x66, 0x66, 0x73, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x61, 0x62, 0x72, 0x20, 0x62, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x3d, 0x38, 0x38, 0x30, 0x20, 0x71, 0x63, 0x6f, 0x6d, 0x70, 0x3d, 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2d, 0x77, 0x72, 0x69, 0x74, 0x65, 0x3d, 0x30, 0x20, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2d, 0x72, 0x65, 0x61, 0x64, 0x3d, 0x30, 0x20, 0x69, 0x70, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, 0x20, 0x70, 0x62, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x33, 0x30, 0x20, 0x61, 0x71, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x31, 0x20, 0x61, 0x71, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x20, 0x63, 0x75, 0x74, 0x72, 0x65, 0x65, 0x20, 0x7a, 0x6f, 0x6e, 0x65, 0x2d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x2d, 0x63, 0x62, 0x72, 0x20, 0x71, 0x67, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3d, 0x33, 0x32, 0x20, 0x6e, 0x6f, 0x2d, 0x72, 0x63, 0x2d, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x20, 0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x36, 0x39, 0x20, 0x71, 0x70, 0x6d, 0x69, 0x6e, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x2d, 0x76, 0x62, 0x76, 0x20, 0x73, 0x61, 0x72, 0x3d, 0x31, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x63, 0x61, 0x6e, 0x3d, 0x30, 0x20, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x3d, 0x35, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x70, 0x72, 0x69, 0x6d, 0x3d, 0x32, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x3d, 0x32, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x3d, 0x32, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x6c, 0x6f, 0x63, 0x3d, 0x30, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x2d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x3d, 0x30, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x63, 0x6c, 0x6c, 0x3d, 0x30, 0x2c, 0x30, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x6c, 0x75, 0x6d, 0x61, 0x3d, 0x30, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x6c, 0x75, 0x6d, 0x61, 0x3d, 0x32, 0x35, 0x35, 0x20, 0x6c, 0x6f, 0x67, 0x32, 0x2d, 0x6d, 0x61, 0x78, 0x2d, 0x70, 0x6f, 0x63, 0x2d, 0x6c, 0x73, 0x62, 0x3d, 0x38, 0x20, 0x76, 0x75, 0x69, 0x2d, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x76, 0x75, 0x69, 0x2d, 0x68, 0x72, 0x64, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x73, 0x3d, 0x31, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x71, 0x70, 0x2d, 0x70, 0x70, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x72, 0x65, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x2d, 0x70, 0x70, 0x73, 0x20, 0x6e, 0x6f, 0x2d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x2d, 0x70, 0x61, 0x73, 0x73, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x72, 0x70, 0x73, 0x20, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x2d, 0x62, 0x69, 0x61, 0x73, 0x3d, 0x30, 0x2e, 0x30, 0x35, 0x20, 0x6e, 0x6f, 0x2d, 0x6f, 0x70, 0x74, 0x2d, 0x63, 0x75, 0x2d, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x2d, 0x71, 0x70, 0x20, 0x6e, 0x6f, 0x2d, 0x61, 0x71, 0x2d, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x64, 0x72, 0x20, 0x6e, 0x6f, 0x2d, 0x68, 0x64, 0x72, 0x2d, 0x6f, 0x70, 0x74, 0x20, 0x6e, 0x6f, 0x2d, 0x64, 0x68, 0x64, 0x72, 0x31, 0x30, 0x2d, 0x6f, 0x70, 0x74, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x2d, 0x72, 0x65, 0x75, 0x73, 0x65, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x3d, 0x35, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2d, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3d, 0x30, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x6d, 0x76, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2d, 0x73, 0x61, 0x6f, 0x20, 0x63, 0x74, 0x75, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x3d, 0x30, 0x20, 0x6e, 0x6f, 0x2d, 0x6c, 0x6f, 0x77, 0x70, 0x61, 0x73, 0x73, 0x2d, 0x64, 0x63, 0x74, 0x20, 0x72, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2d, 0x6d, 0x76, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x2d, 0x70, 0x69, 0x63, 0x3d, 0x31, 0x80, } pck := H265Payloader{} res := pck.Payload(1400, payload) // 1. Aggregating three NALUs into a single payload // 2. Fragmented packets divided by MTU=1400 // 3. Remaining fragment packets split by MTU assert.Len(t, res, 3, "Generated payload should be 3") } func uint8ptr(v uint8) *uint8 { return &v } func uint16ptr(v uint16) *uint16 { return &v } rtp-1.8.27/codecs/opus_packet.go000066400000000000000000000024771512154406600165400ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs // OpusPayloader payloads Opus packets. type OpusPayloader struct{} // Payload fragments an Opus packet across one or more byte arrays. func (p *OpusPayloader) Payload(_ uint16, payload []byte) [][]byte { if payload == nil { return [][]byte{} } out := make([]byte, len(payload)) copy(out, payload) return [][]byte{out} } // OpusPacket represents the Opus header that is stored in the payload of an RTP Packet. type OpusPacket struct { Payload []byte audioDepacketizer } // Unmarshal parses the passed byte slice and stores the result in the OpusPacket this method is called upon. func (p *OpusPacket) Unmarshal(packet []byte) ([]byte, error) { if packet == nil { return nil, errNilPacket } else if len(packet) == 0 { return nil, errShortPacket } p.Payload = packet return packet, nil } // OpusPartitionHeadChecker checks Opus partition head. // // Deprecated: replaced by OpusPacket.IsPartitionHead(). type OpusPartitionHeadChecker struct{} // IsPartitionHead checks whether if this is a head of the Opus partition. // // Deprecated: replaced by OpusPacket.IsPartitionHead(). func (*OpusPartitionHeadChecker) IsPartitionHead(packet []byte) bool { return (&OpusPacket{}).IsPartitionHead(packet) } rtp-1.8.27/codecs/opus_packet_test.go000066400000000000000000000027011512154406600175650ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "testing" "github.com/stretchr/testify/assert" ) func TestOpusPacket_Unmarshal(t *testing.T) { pck := OpusPacket{} // Nil packet raw, err := pck.Unmarshal(nil) assert.ErrorIs(t, err, errNilPacket) assert.Nil(t, raw, "Result should be nil in case of error") // Empty packet raw, err = pck.Unmarshal([]byte{}) assert.ErrorIs(t, err, errShortPacket) assert.Nil(t, raw, "Result should be nil in case of error") // Normal packet raw, err = pck.Unmarshal([]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x90}) assert.NoError(t, err) assert.NotNil(t, raw, "Result shouldn't be nil in case of success") } func TestOpusPayloader_Payload(t *testing.T) { pck := OpusPayloader{} payload := []byte{0x90, 0x90, 0x90} // Positive MTU, nil payload res := pck.Payload(1, nil) assert.Len(t, res, 0, "Generated payload should be empty") // Positive MTU, small payload res = pck.Payload(1, payload) assert.Len(t, res, 1, "Generated payload should be the 1") // Positive MTU, small payload res = pck.Payload(2, payload) assert.Len(t, res, 1, "Generated payload should be the 1") } func TestOpusIsPartitionHead(t *testing.T) { opus := &OpusPacket{} t.Run("NormalPacket", func(t *testing.T) { assert.True( t, opus.IsPartitionHead([]byte{0x00, 0x00}), "All OPUS RTP packet should be the head of a new partition", ) }) } rtp-1.8.27/codecs/vp8_packet.go000066400000000000000000000136331512154406600162630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs // VP8Payloader payloads VP8 packets. type VP8Payloader struct { EnablePictureID bool pictureID uint16 } const ( vp8HeaderSize = 1 ) // Payload fragments a VP8 packet across one or more byte arrays. func (p *VP8Payloader) Payload(mtu uint16, payload []byte) [][]byte { //nolint:cyclop /* * https://tools.ietf.org/html/rfc7741#section-4.2 * * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * |X|R|N|S|R| PID | (REQUIRED) * +-+-+-+-+-+-+-+-+ * X: |I|L|T|K| RSV | (OPTIONAL) * +-+-+-+-+-+-+-+-+ * I: |M| PictureID | (OPTIONAL) * +-+-+-+-+-+-+-+-+ * L: | TL0PICIDX | (OPTIONAL) * +-+-+-+-+-+-+-+-+ * T/K: |TID|Y| KEYIDX | (OPTIONAL) * +-+-+-+-+-+-+-+-+ * S: Start of VP8 partition. SHOULD be set to 1 when the first payload * octet of the RTP packet is the beginning of a new VP8 partition, * and MUST NOT be 1 otherwise. The S bit MUST be set to 1 for the * first packet of each encoded frame. */ usingHeaderSize := vp8HeaderSize if p.EnablePictureID { switch { case p.pictureID == 0: case p.pictureID < 128: usingHeaderSize = vp8HeaderSize + 2 default: usingHeaderSize = vp8HeaderSize + 3 } } maxFragmentSize := int(mtu) - usingHeaderSize payloadData := payload payloadDataRemaining := len(payload) payloadDataIndex := 0 var payloads [][]byte // Make sure the fragment/payload size is correct if minInt(maxFragmentSize, payloadDataRemaining) <= 0 { return payloads } first := true for payloadDataRemaining > 0 { currentFragmentSize := minInt(maxFragmentSize, payloadDataRemaining) out := make([]byte, usingHeaderSize+currentFragmentSize) if first { out[0] = 0x10 first = false } if p.EnablePictureID { switch usingHeaderSize { case vp8HeaderSize: case vp8HeaderSize + 2: out[0] |= 0x80 out[1] |= 0x80 out[2] |= uint8(p.pictureID & 0x7F) // nolint: gosec // G115 false positive case vp8HeaderSize + 3: out[0] |= 0x80 out[1] |= 0x80 out[2] |= 0x80 | uint8((p.pictureID>>8)&0x7F) // nolint: gosec // G115 false positive out[3] |= uint8(p.pictureID & 0xFF) // nolint: gosec // G115 false positive } } copy(out[usingHeaderSize:], payloadData[payloadDataIndex:payloadDataIndex+currentFragmentSize]) payloads = append(payloads, out) payloadDataRemaining -= currentFragmentSize payloadDataIndex += currentFragmentSize } p.pictureID++ p.pictureID &= 0x7FFF return payloads } // VP8Packet represents the VP8 header that is stored in the payload of an RTP Packet. type VP8Packet struct { // Required Header X uint8 /* extended control bits present */ N uint8 /* when set to 1 this frame can be discarded */ S uint8 /* start of VP8 partition */ PID uint8 /* partition index */ // Extended control bits I uint8 /* 1 if PictureID is present */ L uint8 /* 1 if TL0PICIDX is present */ T uint8 /* 1 if TID is present */ K uint8 /* 1 if KEYIDX is present */ // Optional extension PictureID uint16 /* 8 or 16 bits, picture ID */ TL0PICIDX uint8 /* 8 bits temporal level zero index */ TID uint8 /* 2 bits temporal layer index */ Y uint8 /* 1 bit layer sync bit */ KEYIDX uint8 /* 5 bits temporal key frame index */ Payload []byte videoDepacketizer } // Unmarshal parses the passed byte slice and stores the result in the VP8Packet this method is called upon. func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { //nolint:gocognit,cyclop if payload == nil { return nil, errNilPacket } payloadLen := len(payload) payloadIndex := 0 if payloadIndex >= payloadLen { return nil, errShortPacket } p.X = (payload[payloadIndex] & 0x80) >> 7 p.N = (payload[payloadIndex] & 0x20) >> 5 p.S = (payload[payloadIndex] & 0x10) >> 4 p.PID = payload[payloadIndex] & 0x07 payloadIndex++ if p.X == 1 { if payloadIndex >= payloadLen { return nil, errShortPacket } p.I = (payload[payloadIndex] & 0x80) >> 7 p.L = (payload[payloadIndex] & 0x40) >> 6 p.T = (payload[payloadIndex] & 0x20) >> 5 p.K = (payload[payloadIndex] & 0x10) >> 4 payloadIndex++ } else { p.I = 0 p.L = 0 p.T = 0 p.K = 0 } // nolint: nestif if p.I == 1 { // PID present? if payloadIndex >= payloadLen { return nil, errShortPacket } if payload[payloadIndex]&0x80 > 0 { // M == 1, PID is 16bit if payloadIndex+1 >= payloadLen { return nil, errShortPacket } p.PictureID = (uint16(payload[payloadIndex]&0x7F) << 8) | uint16(payload[payloadIndex+1]) payloadIndex += 2 } else { p.PictureID = uint16(payload[payloadIndex]) payloadIndex++ } } else { p.PictureID = 0 } if p.L == 1 { if payloadIndex >= payloadLen { return nil, errShortPacket } p.TL0PICIDX = payload[payloadIndex] payloadIndex++ } else { p.TL0PICIDX = 0 } if p.T == 1 || p.K == 1 { // nolint: nestif if payloadIndex >= payloadLen { return nil, errShortPacket } if p.T == 1 { p.TID = payload[payloadIndex] >> 6 p.Y = (payload[payloadIndex] >> 5) & 0x1 } else { p.TID = 0 p.Y = 0 } if p.K == 1 { p.KEYIDX = payload[payloadIndex] & 0x1F } else { p.KEYIDX = 0 } payloadIndex++ } else { p.TID = 0 p.Y = 0 p.KEYIDX = 0 } p.Payload = payload[payloadIndex:] return p.Payload, nil } // VP8PartitionHeadChecker checks VP8 partition head // // Deprecated: replaced by VP8Packet.IsPartitionHead(). type VP8PartitionHeadChecker struct{} // IsPartitionHead checks whether if this is a head of the VP8 partition. // // Deprecated: replaced by VP8Packet.IsPartitionHead(). func (*VP8PartitionHeadChecker) IsPartitionHead(packet []byte) bool { return (&VP8Packet{}).IsPartitionHead(packet) } // IsPartitionHead checks whether if this is a head of the VP8 partition. func (*VP8Packet) IsPartitionHead(payload []byte) bool { if len(payload) < 1 { return false } return (payload[0] & 0x10) != 0 } rtp-1.8.27/codecs/vp8_packet_test.go000066400000000000000000000123211512154406600173130ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "testing" "github.com/stretchr/testify/assert" ) func TestVP8Packet_Unmarshal(t *testing.T) { pck := VP8Packet{} // Nil packet raw, err := pck.Unmarshal(nil) assert.ErrorIs(t, err, errNilPacket) assert.Nil(t, raw, "Result should be nil in case of error") // Nil payload raw, err = pck.Unmarshal([]byte{}) assert.ErrorIs(t, err, errShortPacket) assert.Nil(t, raw, "Result should be nil in case of error") // Normal payload raw, err = pck.Unmarshal([]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x90}) assert.NoError(t, err) assert.NotNil(t, raw, "Result shouldn't be nil in case of success") // Header size, only X raw, err = pck.Unmarshal([]byte{0x80, 0x00, 0x00, 0x00}) assert.NoError(t, err) assert.NotNil(t, raw, "Result shouldn't be nil in case of success") // Header size, X and I raw, err = pck.Unmarshal([]byte{0x80, 0x80, 0x00, 0x00}) assert.NoError(t, err) assert.NotNil(t, raw, "Result shouldn't be nil in case of success") // Header size, X and I, PID 16bits raw, err = pck.Unmarshal([]byte{0x80, 0x80, 0x81, 0x00}) assert.NoError(t, err) assert.NotNil(t, raw, "Result shouldn't be nil in case of success") // Header size, X and L raw, err = pck.Unmarshal([]byte{0x80, 0x40, 0x00, 0x00}) assert.NoError(t, err) assert.NotNil(t, raw, "Result shouldn't be nil in case of success") // Header size, X and T raw, err = pck.Unmarshal([]byte{0x80, 0x20, 0x00, 0x00}) assert.NoError(t, err) assert.NotNil(t, raw, "Result shouldn't be nil in case of success") // Header size, X and K raw, err = pck.Unmarshal([]byte{0x80, 0x10, 0x00, 0x00}) assert.NoError(t, err) assert.NotNil(t, raw, "Result shouldn't be nil in case of success") // Header size, all flags raw, err = pck.Unmarshal([]byte{0xff, 0xff, 0x00, 0x00}) assert.ErrorIs(t, err, errShortPacket) assert.Nil(t, raw, "Result should be nil in case of error") // According to RFC 7741 Section 4.4, the packetizer need not pay // attention to partition boundaries. In that case, it may // produce packets with minimal headers. // The next three have been witnessed in nature. _, err = pck.Unmarshal([]byte{0x00}) assert.NoError(t, err, "Empty packet with trivial header") _, err = pck.Unmarshal([]byte{0x00, 0x2a, 0x94}) assert.NoError(t, err, "Non-empty packet with trivial header") raw, err = pck.Unmarshal([]byte{0x81, 0x81, 0x94}) assert.ErrorIs(t, err, errShortPacket) assert.Nil(t, raw, "Result should be nil in case of error") // The following two were invented. _, err = pck.Unmarshal([]byte{0x80, 0x00}) assert.NoError(t, err, "Empty packet with trivial extension") _, err = pck.Unmarshal([]byte{0x80, 0x80, 42}) assert.NoError(t, err, "Header with PictureID") } func TestVP8Payloader_Payload(t *testing.T) { testCases := map[string]struct { payloader VP8Payloader mtu uint16 payload [][]byte expected [][][]byte }{ "WithoutPictureID": { payloader: VP8Payloader{}, mtu: 2, payload: [][]byte{ {0x90, 0x90, 0x90}, {0x91, 0x91}, }, expected: [][][]byte{ {{0x10, 0x90}, {0x00, 0x90}, {0x00, 0x90}}, {{0x10, 0x91}, {0x00, 0x91}}, }, }, "WithPictureID_1byte": { payloader: VP8Payloader{ EnablePictureID: true, pictureID: 0x20, }, mtu: 5, payload: [][]byte{ {0x90, 0x90, 0x90}, {0x91, 0x91}, }, expected: [][][]byte{ { {0x90, 0x80, 0x20, 0x90, 0x90}, {0x80, 0x80, 0x20, 0x90}, }, { {0x90, 0x80, 0x21, 0x91, 0x91}, }, }, }, "WithPictureID_2bytes": { payloader: VP8Payloader{ EnablePictureID: true, pictureID: 0x120, }, mtu: 6, payload: [][]byte{ {0x90, 0x90, 0x90}, {0x91, 0x91}, }, expected: [][][]byte{ { {0x90, 0x80, 0x81, 0x20, 0x90, 0x90}, {0x80, 0x80, 0x81, 0x20, 0x90}, }, { {0x90, 0x80, 0x81, 0x21, 0x91, 0x91}, }, }, }, } for name, testCase := range testCases { testCase := testCase t.Run(name, func(t *testing.T) { pck := testCase.payloader for i := range testCase.payload { res := pck.Payload(testCase.mtu, testCase.payload[i]) assert.Equal(t, testCase.expected[i], res, "Generated packet differs") } }) } t.Run("Error", func(t *testing.T) { pck := VP8Payloader{} payload := []byte{0x90, 0x90, 0x90} // Positive MTU, nil payload res := pck.Payload(1, nil) assert.Len(t, res, 0, "Generated payload should be empty") // Positive MTU, small payload // MTU of 1 results in fragment size of 0 res = pck.Payload(1, payload) assert.Len(t, res, 0, "Generated payload should be empty") }) } func TestVP8IsPartitionHead(t *testing.T) { vp8 := &VP8Packet{} t.Run("SmallPacket", func(t *testing.T) { assert.False(t, vp8.IsPartitionHead([]byte{0x00}), "Small packet should not be the head of a new partition") }) t.Run("SFlagON", func(t *testing.T) { assert.True( t, vp8.IsPartitionHead([]byte{0x10, 0x00, 0x00, 0x00}), "Packet with S flag should be the head of a new partition", ) }) t.Run("SFlagOFF", func(t *testing.T) { assert.False( t, vp8.IsPartitionHead([]byte{0x00, 0x00, 0x00, 0x00}), "Packet without S flag should not be the head of a new partition", ) }) } rtp-1.8.27/codecs/vp9/000077500000000000000000000000001512154406600144005ustar00rootroot00000000000000rtp-1.8.27/codecs/vp9/bits.go000066400000000000000000000023141512154406600156700ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package vp9 import "errors" var errNotEnoughBits = errors.New("not enough bits") func hasSpace(buf []byte, pos int, n int) error { if n > ((len(buf) * 8) - pos) { return errNotEnoughBits } return nil } func readFlag(buf []byte, pos *int) (bool, error) { err := hasSpace(buf, *pos, 1) if err != nil { return false, err } return readFlagUnsafe(buf, pos), nil } func readFlagUnsafe(buf []byte, pos *int) bool { b := (buf[*pos>>0x03] >> (7 - (*pos & 0x07))) & 0x01 *pos++ return b == 1 } func readBits(buf []byte, pos *int, n int) (uint64, error) { err := hasSpace(buf, *pos, n) if err != nil { return 0, err } return readBitsUnsafe(buf, pos, n), nil } func readBitsUnsafe(buf []byte, pos *int, n int) uint64 { res := 8 - (*pos & 0x07) if n < res { bits := uint64((buf[*pos>>0x03] >> (res - n)) & (1<>0x03] & (1<= 8 { bits = (bits << 8) | uint64(buf[*pos>>0x03]) *pos += 8 n -= 8 } if n > 0 { bits = (bits << n) | uint64(buf[*pos>>0x03]>>(8-n)) *pos += n } return bits } rtp-1.8.27/codecs/vp9/header.go000066400000000000000000000115651512154406600161670ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT // Package vp9 contains a VP9 header parser. package vp9 import ( "errors" ) var ( errInvalidFrameMarker = errors.New("invalid frame marker") errWrongFrameSyncByte0 = errors.New("wrong frame_sync_byte_0") errWrongFrameSyncByte1 = errors.New("wrong frame_sync_byte_1") errWrongFrameSyncByte2 = errors.New("wrong frame_sync_byte_2") ) // HeaderColorConfig is the color_config member of an header. type HeaderColorConfig struct { TenOrTwelveBit bool BitDepth uint8 ColorSpace uint8 ColorRange bool SubsamplingX bool SubsamplingY bool } func (c *HeaderColorConfig) unmarshal(profile uint8, buf []byte, pos *int) error { // nolint:cyclop if profile >= 2 { var err error c.TenOrTwelveBit, err = readFlag(buf, pos) if err != nil { return err } if c.TenOrTwelveBit { c.BitDepth = 12 } else { c.BitDepth = 10 } } else { c.BitDepth = 8 } tmp, err := readBits(buf, pos, 3) if err != nil { return err } c.ColorSpace = uint8(tmp) // nolint: gosec // G115, no overflow we read 3 bits if c.ColorSpace != 7 { // nolint: nestif var err error c.ColorRange, err = readFlag(buf, pos) if err != nil { return err } if profile == 1 || profile == 3 { err := hasSpace(buf, *pos, 3) if err != nil { return err } c.SubsamplingX = readFlagUnsafe(buf, pos) c.SubsamplingY = readFlagUnsafe(buf, pos) *pos++ } else { c.SubsamplingX = true c.SubsamplingY = true } } else { c.ColorRange = true if profile == 1 || profile == 3 { c.SubsamplingX = false c.SubsamplingY = false err := hasSpace(buf, *pos, 1) if err != nil { return err } *pos++ } } return nil } // HeaderFrameSize is the frame_size member of an header. type HeaderFrameSize struct { FrameWidthMinus1 uint16 FrameHeightMinus1 uint16 } func (s *HeaderFrameSize) unmarshal(buf []byte, pos *int) error { err := hasSpace(buf, *pos, 32) if err != nil { return err } s.FrameWidthMinus1 = uint16(readBitsUnsafe(buf, pos, 16)) // nolint: gosec // G115 no overflow, we read 16 bits s.FrameHeightMinus1 = uint16(readBitsUnsafe(buf, pos, 16)) // nolint: gosec // G115 return nil } // Header is a VP9 Frame header. // Specification: // https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf type Header struct { Profile uint8 ShowExistingFrame bool FrameToShowMapIdx uint8 NonKeyFrame bool ShowFrame bool ErrorResilientMode bool ColorConfig *HeaderColorConfig FrameSize *HeaderFrameSize } // Unmarshal decodes a Header. func (h *Header) Unmarshal(buf []byte) error { //nolint:cyclop pos := 0 err := hasSpace(buf, pos, 4) if err != nil { return err } frameMarker := readBitsUnsafe(buf, &pos, 2) if frameMarker != 2 { return errInvalidFrameMarker } profileLowBit := uint8(readBitsUnsafe(buf, &pos, 1)) // nolint: gosec // no overflow, we read 1 bit profileHighBit := uint8(readBitsUnsafe(buf, &pos, 1)) // nolint: gosec // G115 h.Profile = profileHighBit<<1 + profileLowBit if h.Profile == 3 { err = hasSpace(buf, pos, 1) if err != nil { return err } pos++ } h.ShowExistingFrame, err = readFlag(buf, &pos) if err != nil { return err } if h.ShowExistingFrame { var tmp uint64 tmp, err = readBits(buf, &pos, 3) if err != nil { return err } h.FrameToShowMapIdx = uint8(tmp) // nolint: gosec // no overflow, we read 3 bits return nil } err = hasSpace(buf, pos, 3) if err != nil { return err } h.NonKeyFrame = readFlagUnsafe(buf, &pos) h.ShowFrame = readFlagUnsafe(buf, &pos) h.ErrorResilientMode = readFlagUnsafe(buf, &pos) if !h.NonKeyFrame { // nolint: nestif err := hasSpace(buf, pos, 24) if err != nil { return err } frameSyncByte0 := uint8(readBitsUnsafe(buf, &pos, 8)) // nolint: gosec // no overflow, we read 8 bits if frameSyncByte0 != 0x49 { return errWrongFrameSyncByte0 } frameSyncByte1 := uint8(readBitsUnsafe(buf, &pos, 8)) // nolint: gosec // no overflow, we read 8 bits if frameSyncByte1 != 0x83 { return errWrongFrameSyncByte1 } frameSyncByte2 := uint8(readBitsUnsafe(buf, &pos, 8)) // nolint: gosec // no overflow, we read 8 bits if frameSyncByte2 != 0x42 { return errWrongFrameSyncByte2 } h.ColorConfig = &HeaderColorConfig{} err = h.ColorConfig.unmarshal(h.Profile, buf, &pos) if err != nil { return err } h.FrameSize = &HeaderFrameSize{} err = h.FrameSize.unmarshal(buf, &pos) if err != nil { return err } } return nil } // Width returns the video width. func (h Header) Width() uint16 { if h.FrameSize == nil { return 0 } return h.FrameSize.FrameWidthMinus1 + 1 } // Height returns the video height. func (h Header) Height() uint16 { if h.FrameSize == nil { return 0 } return h.FrameSize.FrameHeightMinus1 + 1 } rtp-1.8.27/codecs/vp9/header_test.go000066400000000000000000000062271512154406600172250ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package vp9 import ( "testing" "github.com/stretchr/testify/assert" ) func TestHeaderUnmarshal(t *testing.T) { cases := []struct { name string byts []byte sh Header width uint16 height uint16 }{ { "chrome webrtc", []byte{ 0x82, 0x49, 0x83, 0x42, 0x00, 0x77, 0xf0, 0x32, 0x34, 0x30, 0x38, 0x24, 0x1c, 0x19, 0x40, 0x18, 0x03, 0x40, 0x5f, 0xb4, }, Header{ ShowFrame: true, ColorConfig: &HeaderColorConfig{ BitDepth: 8, SubsamplingX: true, SubsamplingY: true, }, FrameSize: &HeaderFrameSize{ FrameWidthMinus1: 1919, FrameHeightMinus1: 803, }, }, 1920, 804, }, { "vp9 sample", []byte{ 0x82, 0x49, 0x83, 0x42, 0x40, 0xef, 0xf0, 0x86, 0xf4, 0x04, 0x21, 0xa0, 0xe0, 0x00, 0x30, 0x70, 0x00, 0x00, 0x00, 0x01, }, Header{ ShowFrame: true, ColorConfig: &HeaderColorConfig{ BitDepth: 8, ColorSpace: 2, SubsamplingX: true, SubsamplingY: true, }, FrameSize: &HeaderFrameSize{ FrameWidthMinus1: 3839, FrameHeightMinus1: 2159, }, }, 3840, 2160, }, { "show existing frame", []byte{ 0b10101010, 0x49, 0x83, 0x42, 0x40, 0xef, 0xf0, 0x86, 0xf4, 0x04, 0x21, 0xa0, 0xe0, 0x00, 0x30, 0x70, 0x00, 0x00, 0x00, 0x01, }, Header{ Profile: 1, ShowExistingFrame: true, FrameToShowMapIdx: 2, }, 0, 0, }, { "profile 0", []byte{ 0x92, 0x49, 0x83, 0x42, 0x40, 0xef, 0xf0, 0x86, 0xf4, 0x04, 0x21, 0xa0, 0xe0, 0x00, 0x30, 0x70, 0x00, 0x00, 0x00, 0x01, }, Header{ Profile: 2, ShowFrame: true, ColorConfig: &HeaderColorConfig{ BitDepth: 10, ColorSpace: 4, SubsamplingX: true, SubsamplingY: true, }, FrameSize: &HeaderFrameSize{ FrameWidthMinus1: 7678, FrameHeightMinus1: 4318, }, }, 0x1dff, 0x10df, }, { "profile 1", []byte{ 0b10100010, 0x49, 0x83, 0b1000010, 0b11000000, 0xef, 0xf0, 0x86, 0xf4, 0x04, 0x21, 0xa0, 0xe0, 0x00, 0x30, 0x70, 0x00, 0x00, 0x00, 0x01, }, Header{ Profile: 1, ShowFrame: true, ColorConfig: &HeaderColorConfig{ BitDepth: 8, ColorSpace: 6, }, FrameSize: &HeaderFrameSize{ FrameWidthMinus1: 30712, FrameHeightMinus1: 17274, }, }, 0x77f9, 0x437b, }, { "ColorRange = true", []byte{ 0b10100010, 0x49, 0x83, 0b1000010, 0b11100000, 0xef, 0xf0, 0x86, 0xf4, 0x04, 0x21, 0xa0, 0xe0, 0x00, 0x30, 0x70, 0x00, 0x00, 0x00, 0x01, }, Header{ Profile: 1, ShowFrame: true, ColorConfig: &HeaderColorConfig{ BitDepth: 8, ColorSpace: 7, ColorRange: true, }, FrameSize: &HeaderFrameSize{ FrameWidthMinus1: 3839, FrameHeightMinus1: 2159, }, }, 0xf00, 0x870, }, } for _, ca := range cases { t.Run(ca.name, func(t *testing.T) { var sh Header assert.NoError(t, sh.Unmarshal(ca.byts)) assert.Equal(t, ca.sh, sh) assert.Equal(t, ca.width, sh.Width()) assert.Equal(t, ca.height, sh.Height()) }) } } rtp-1.8.27/codecs/vp9_packet.go000066400000000000000000000302301512154406600162540ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "github.com/pion/randutil" "github.com/pion/rtp/codecs/vp9" ) // Use global random generator to properly seed by crypto grade random. var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals // VP9Payloader payloads VP9 packets. type VP9Payloader struct { // whether to use flexible mode or non-flexible mode. FlexibleMode bool // InitialPictureIDFn is a function that returns random initial picture ID. InitialPictureIDFn func() uint16 pictureID uint16 initialized bool } const ( maxSpatialLayers = 5 maxVP9RefPics = 3 ) // Payload fragments an VP9 packet across one or more byte arrays. func (p *VP9Payloader) Payload(mtu uint16, payload []byte) [][]byte { if !p.initialized { if p.InitialPictureIDFn == nil { p.InitialPictureIDFn = func() uint16 { return uint16(globalMathRandomGenerator.Intn(0x7FFF)) // nolint: gosec } } p.pictureID = p.InitialPictureIDFn() & 0x7FFF p.initialized = true } var payloads [][]byte if p.FlexibleMode { payloads = p.payloadFlexible(mtu, payload) } else { payloads = p.payloadNonFlexible(mtu, payload) } p.pictureID++ if p.pictureID >= 0x8000 { p.pictureID = 0 } return payloads } func (p *VP9Payloader) payloadFlexible(mtu uint16, payload []byte) [][]byte { /* * Flexible mode (F=1) * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * |I|P|L|F|B|E|V|Z| (REQUIRED) * +-+-+-+-+-+-+-+-+ * I: |M| PICTURE ID | (REQUIRED) * +-+-+-+-+-+-+-+-+ * M: | EXTENDED PID | (RECOMMENDED) * +-+-+-+-+-+-+-+-+ * L: | TID |U| SID |D| (CONDITIONALLY RECOMMENDED) * +-+-+-+-+-+-+-+-+ -\ * P,F: | P_DIFF |N| (CONDITIONALLY REQUIRED) - up to 3 times * +-+-+-+-+-+-+-+-+ -/ * V: | SS | * | .. | * +-+-+-+-+-+-+-+-+ */ headerSize := 3 maxFragmentSize := int(mtu) - headerSize payloadDataRemaining := len(payload) payloadDataIndex := 0 var payloads [][]byte if minInt(maxFragmentSize, payloadDataRemaining) <= 0 { return [][]byte{} } for payloadDataRemaining > 0 { currentFragmentSize := minInt(maxFragmentSize, payloadDataRemaining) out := make([]byte, headerSize+currentFragmentSize) out[0] = 0x90 // F=1, I=1 if payloadDataIndex == 0 { out[0] |= 0x08 // B=1 } if payloadDataRemaining == currentFragmentSize { out[0] |= 0x04 // E=1 } out[1] = byte(p.pictureID>>8) | 0x80 out[2] = byte(p.pictureID) copy(out[headerSize:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize]) payloads = append(payloads, out) payloadDataRemaining -= currentFragmentSize payloadDataIndex += currentFragmentSize } return payloads } func (p *VP9Payloader) payloadNonFlexible(mtu uint16, payload []byte) [][]byte { //nolint:cyclop /* * Non-flexible mode (F=0) * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * |I|P|L|F|B|E|V|Z| (REQUIRED) * +-+-+-+-+-+-+-+-+ * I: |M| PICTURE ID | (RECOMMENDED) * +-+-+-+-+-+-+-+-+ * M: | EXTENDED PID | (RECOMMENDED) * +-+-+-+-+-+-+-+-+ * L: | TID |U| SID |D| (CONDITIONALLY RECOMMENDED) * +-+-+-+-+-+-+-+-+ * | TL0PICIDX | (CONDITIONALLY REQUIRED) * +-+-+-+-+-+-+-+-+ * V: | SS | * | .. | * +-+-+-+-+-+-+-+-+ */ var header vp9.Header err := header.Unmarshal(payload) if err != nil { return [][]byte{} } payloadDataRemaining := len(payload) payloadDataIndex := 0 var payloads [][]byte for payloadDataRemaining > 0 { var headerSize int if !header.NonKeyFrame && payloadDataIndex == 0 { headerSize = 3 + 8 } else { headerSize = 3 } maxFragmentSize := int(mtu) - headerSize currentFragmentSize := minInt(maxFragmentSize, payloadDataRemaining) if currentFragmentSize <= 0 { return [][]byte{} } out := make([]byte, headerSize+currentFragmentSize) out[0] = 0x80 | 0x01 // I=1, Z=1 if header.NonKeyFrame { out[0] |= 0x40 // P=1 } if payloadDataIndex == 0 { out[0] |= 0x08 // B=1 } if payloadDataRemaining == currentFragmentSize { out[0] |= 0x04 // E=1 } out[1] = byte(p.pictureID>>8) | 0x80 out[2] = byte(p.pictureID) off := 3 if !header.NonKeyFrame && payloadDataIndex == 0 { out[0] |= 0x02 // V=1 out[off] = 0x10 | 0x08 // N_S=0, Y=1, G=1 off++ width := header.Width() out[off] = byte(width >> 8) off++ out[off] = byte(width & 0xFF) off++ height := header.Height() out[off] = byte(height >> 8) off++ out[off] = byte(height & 0xFF) off++ out[off] = 0x01 // N_G=1 off++ out[off] = 1<<4 | 1<<2 // TID=0, U=1, R=1 off++ out[off] = 0x01 // P_DIFF=1 } copy(out[headerSize:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize]) payloads = append(payloads, out) payloadDataRemaining -= currentFragmentSize payloadDataIndex += currentFragmentSize } return payloads } // VP9Packet represents the VP9 header that is stored in the payload of an RTP Packet. type VP9Packet struct { // Required header I bool // PictureID is present P bool // Inter-picture predicted frame L bool // Layer indices is present F bool // Flexible mode B bool // Start of a frame E bool // End of a frame V bool // Scalability structure (SS) data present Z bool // Not a reference frame for upper spatial layers // Recommended headers PictureID uint16 // 7 or 16 bits, picture ID // Conditionally recommended headers TID uint8 // Temporal layer ID U bool // Switching up point SID uint8 // Spatial layer ID D bool // Inter-layer dependency used // Conditionally required headers PDiff []uint8 // Reference index (F=1) TL0PICIDX uint8 // Temporal layer zero index (F=0) // Scalability structure headers NS uint8 // N_S + 1 indicates the number of spatial layers present in the VP9 stream Y bool // Each spatial layer's frame resolution present G bool // PG description present flag. NG uint8 // N_G indicates the number of pictures in a Picture Group (PG) Width []uint16 Height []uint16 PGTID []uint8 // Temporal layer ID of pictures in a Picture Group PGU []bool // Switching up point of pictures in a Picture Group PGPDiff [][]uint8 // Reference indecies of pictures in a Picture Group Payload []byte videoDepacketizer } // Unmarshal parses the passed byte slice and stores the result in the VP9Packet this method is called upon. func (p *VP9Packet) Unmarshal(packet []byte) ([]byte, error) { // nolint:cyclop if packet == nil { return nil, errNilPacket } if len(packet) < 1 { return nil, errShortPacket } p.I = packet[0]&0x80 != 0 p.P = packet[0]&0x40 != 0 p.L = packet[0]&0x20 != 0 p.F = packet[0]&0x10 != 0 p.B = packet[0]&0x08 != 0 p.E = packet[0]&0x04 != 0 p.V = packet[0]&0x02 != 0 p.Z = packet[0]&0x01 != 0 pos := 1 var err error if p.I { pos, err = p.parsePictureID(packet, pos) if err != nil { return nil, err } } if p.L { pos, err = p.parseLayerInfo(packet, pos) if err != nil { return nil, err } } if p.F && p.P { pos, err = p.parseRefIndices(packet, pos) if err != nil { return nil, err } } if p.V { pos, err = p.parseSSData(packet, pos) if err != nil { return nil, err } } p.Payload = packet[pos:] return p.Payload, nil } // Picture ID: /* * +-+-+-+-+-+-+-+-+ * I: |M| PICTURE ID | M:0 => picture id is 7 bits. * +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. * M: | EXTENDED PID | * +-+-+-+-+-+-+-+-+ **/ // . func (p *VP9Packet) parsePictureID(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket } p.PictureID = uint16(packet[pos] & 0x7F) if packet[pos]&0x80 != 0 { pos++ if len(packet) <= pos { return pos, errShortPacket } p.PictureID = p.PictureID<<8 | uint16(packet[pos]) } pos++ return pos, nil } func (p *VP9Packet) parseLayerInfo(packet []byte, pos int) (int, error) { pos, err := p.parseLayerInfoCommon(packet, pos) if err != nil { return pos, err } if p.F { return pos, nil } return p.parseLayerInfoNonFlexibleMode(packet, pos) } // Layer indices (flexible mode): /* * +-+-+-+-+-+-+-+-+ * L: | T |U| S |D| * +-+-+-+-+-+-+-+-+ **/ // . func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket } p.TID = packet[pos] >> 5 p.U = packet[pos]&0x10 != 0 p.SID = (packet[pos] >> 1) & 0x7 p.D = packet[pos]&0x01 != 0 if p.SID >= maxSpatialLayers { return pos, errTooManySpatialLayers } pos++ return pos, nil } // Layer indices (non-flexible mode): /* * +-+-+-+-+-+-+-+-+ * L: | T |U| S |D| * +-+-+-+-+-+-+-+-+ * | TL0PICIDX | * +-+-+-+-+-+-+-+-+ **/ // . func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket } p.TL0PICIDX = packet[pos] pos++ return pos, nil } // Reference indices: . /* * +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index * P,F: | P_DIFF |N| up to 3 times has to be specified. * +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows * current P_DIFF. * **/ // . func (p *VP9Packet) parseRefIndices(packet []byte, pos int) (int, error) { for { if len(packet) <= pos { return pos, errShortPacket } p.PDiff = append(p.PDiff, packet[pos]>>1) if packet[pos]&0x01 == 0 { break } if len(p.PDiff) >= maxVP9RefPics { return pos, errTooManyPDiff } pos++ } pos++ return pos, nil } // Scalability structure (SS): /* * +-+-+-+-+-+-+-+-+ * V: | N_S |Y|G|-|-|-| * +-+-+-+-+-+-+-+-+ -| * Y: | WIDTH | (OPTIONAL) . * + . * | | (OPTIONAL) . * +-+-+-+-+-+-+-+-+ . N_S + 1 times * | HEIGHT | (OPTIONAL) . * + . * | | (OPTIONAL) . * +-+-+-+-+-+-+-+-+ -| * G: | N_G | (OPTIONAL) * +-+-+-+-+-+-+-+-+ -| * N_G: | T |U| R |-|-| (OPTIONAL) . * +-+-+-+-+-+-+-+-+ -| . N_G times * | P_DIFF | (OPTIONAL) . R times . * +-+-+-+-+-+-+-+-+ -| -| **/ // . func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) { // nolint: cyclop if len(packet) <= pos { return pos, errShortPacket } p.NS = packet[pos] >> 5 p.Y = packet[pos]&0x10 != 0 p.G = packet[pos]&0x8 != 0 pos++ NS := p.NS + 1 p.NG = 0 if p.Y { p.Width = make([]uint16, NS) p.Height = make([]uint16, NS) for i := 0; i < int(NS); i++ { if len(packet) <= (pos + 3) { return pos, errShortPacket } p.Width[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) pos += 2 p.Height[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) pos += 2 } } if p.G { if len(packet) <= pos { return pos, errShortPacket } p.NG = packet[pos] pos++ } for i := 0; i < int(p.NG); i++ { if len(packet) <= pos { return pos, errShortPacket } p.PGTID = append(p.PGTID, packet[pos]>>5) p.PGU = append(p.PGU, packet[pos]&0x10 != 0) R := (packet[pos] >> 2) & 0x3 pos++ p.PGPDiff = append(p.PGPDiff, []uint8{}) if len(packet) <= (pos + int(R) - 1) { return pos, errShortPacket } for j := 0; j < int(R); j++ { p.PGPDiff[i] = append(p.PGPDiff[i], packet[pos]) pos++ } } return pos, nil } // VP9PartitionHeadChecker checks VP9 partition head. // // Deprecated: replaced by VP9Packet.IsPartitionHead(). type VP9PartitionHeadChecker struct{} // IsPartitionHead checks whether if this is a head of the VP9 partition. // // Deprecated: replaced by VP9Packet.IsPartitionHead(). func (*VP9PartitionHeadChecker) IsPartitionHead(packet []byte) bool { return (&VP9Packet{}).IsPartitionHead(packet) } // IsPartitionHead checks whether if this is a head of the VP9 partition. func (*VP9Packet) IsPartitionHead(payload []byte) bool { if len(payload) < 1 { return false } return (payload[0] & 0x08) != 0 } rtp-1.8.27/codecs/vp9_packet_test.go000066400000000000000000000231511512154406600173170ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package codecs import ( "math/rand" "testing" "github.com/stretchr/testify/assert" ) func TestVP9Packet_Unmarshal(t *testing.T) { cases := map[string]struct { b []byte pkt VP9Packet err error }{ "Nil": { b: nil, err: errNilPacket, }, "Empty": { b: []byte{}, err: errShortPacket, }, "NonFlexible": { b: []byte{0x00, 0xAA}, pkt: VP9Packet{ Payload: []byte{0xAA}, }, }, "NonFlexiblePictureID": { b: []byte{0x80, 0x02, 0xAA}, pkt: VP9Packet{ I: true, PictureID: 0x02, Payload: []byte{0xAA}, }, }, "NonFlexiblePictureIDExt": { b: []byte{0x80, 0x81, 0xFF, 0xAA}, pkt: VP9Packet{ I: true, PictureID: 0x01FF, Payload: []byte{0xAA}, }, }, "NonFlexiblePictureIDExt_ShortPacket0": { b: []byte{0x80, 0x81}, err: errShortPacket, }, "NonFlexiblePictureIDExt_ShortPacket1": { b: []byte{0x80}, err: errShortPacket, }, "NonFlexibleLayerIndicePictureID": { b: []byte{0xA0, 0x02, 0x23, 0x01, 0xAA}, pkt: VP9Packet{ I: true, L: true, PictureID: 0x02, TID: 0x01, SID: 0x01, D: true, TL0PICIDX: 0x01, Payload: []byte{0xAA}, }, }, "FlexibleLayerIndicePictureID": { b: []byte{0xB0, 0x02, 0x23, 0x01, 0xAA}, pkt: VP9Packet{ F: true, I: true, L: true, PictureID: 0x02, TID: 0x01, SID: 0x01, D: true, Payload: []byte{0x01, 0xAA}, }, }, "NonFlexibleLayerIndicePictureID_ShortPacket0": { b: []byte{0xA0, 0x02, 0x23}, err: errShortPacket, }, "NonFlexibleLayerIndicePictureID_ShortPacket1": { b: []byte{0xA0, 0x02}, err: errShortPacket, }, "FlexiblePictureIDRefIndex": { b: []byte{0xD0, 0x02, 0x03, 0x04, 0xAA}, pkt: VP9Packet{ I: true, P: true, F: true, PictureID: 0x02, PDiff: []uint8{0x01, 0x02}, Payload: []byte{0xAA}, }, }, "FlexiblePictureIDRefIndex_TooManyPDiff": { b: []byte{0xD0, 0x02, 0x03, 0x05, 0x07, 0x09, 0x10, 0xAA}, err: errTooManyPDiff, }, "FlexiblePictureIDRefIndexNoPayload": { b: []byte{0xD0, 0x02, 0x03, 0x04}, pkt: VP9Packet{ I: true, P: true, F: true, PictureID: 0x02, PDiff: []uint8{0x01, 0x02}, Payload: []byte{}, }, }, "FlexiblePictureIDRefIndex_ShortPacket0": { b: []byte{0xD0, 0x02, 0x03}, err: errShortPacket, }, "FlexiblePictureIDRefIndex_ShortPacket1": { b: []byte{0xD0, 0x02}, err: errShortPacket, }, "FlexiblePictureIDRefIndex_ShortPacket2": { b: []byte{0xD0}, err: errShortPacket, }, "ScalabilityStructureResolutionsNoPayload": { b: []byte{ 0x0A, (1 << 5) | (1 << 4), // NS:1 Y:1 G:0 640 >> 8, 640 & 0xff, 360 >> 8, 360 & 0xff, 1280 >> 8, 1280 & 0xff, 720 >> 8, 720 & 0xff, }, pkt: VP9Packet{ B: true, V: true, NS: 1, Y: true, G: false, NG: 0, Width: []uint16{640, 1280}, Height: []uint16{360, 720}, Payload: []byte{}, }, }, "ScalabilityStructureNoPayload": { b: []byte{ 0x0A, (1 << 5) | (0 << 4) | (1 << 3), // NS:1 Y:0 G:1 2, (0 << 5) | (1 << 4) | (0 << 2), // T:0 U:1 R:0 - (2 << 5) | (0 << 4) | (1 << 2), // T:2 U:0 R:1 - 33, }, pkt: VP9Packet{ B: true, V: true, NS: 1, Y: false, G: true, NG: 2, PGTID: []uint8{0, 2}, PGU: []bool{true, false}, PGPDiff: [][]uint8{{}, {33}}, Payload: []byte{}, }, }, "ScalabilityStructureReserved": { b: []byte{ 0x0A, (1 << 5) | (0 << 4) | (0 << 3) | (1 << 2) | (1 << 1) | 1, // NS:1 Y:0 G:0, reserved fields set to 1 }, pkt: VP9Packet{ B: true, V: true, NS: 1, Y: false, G: false, NG: 0, Payload: []byte{}, }, }, "ScalabilityStructure_ShortPacket0": { b: []byte{0x0A, 0x10}, err: errShortPacket, }, "ScalabilityMissingWidth": { b: []byte("200"), err: errShortPacket, }, "ScalabilityMissingNG": { b: []byte("b00800000000"), err: errShortPacket, }, "ScalabilityMissingTemporalLayerIDs": { b: []byte("20H0"), err: errShortPacket, }, "ScalabilityMissingReferenceIndices": { b: []byte("20H007"), err: errShortPacket, }, } for name, testCase := range cases { testCase := testCase t.Run(name, func(t *testing.T) { p := VP9Packet{} raw, err := p.Unmarshal(testCase.b) if testCase.err == nil { assert.NoError(t, err) assert.NotNil(t, raw) assert.Equal(t, testCase.pkt, p) } else { assert.Nil(t, raw, "Result should be nil in case of error") assert.ErrorIs(t, err, testCase.err) } }) } } func TestVP9Payloader_Payload(t *testing.T) { r0 := int(rand.New(rand.NewSource(0)).Int31n(0x7FFF)) //nolint:gosec var rands [][2]byte for i := 0; i < 10; i++ { rands = append(rands, [2]byte{byte(r0>>8) | 0x80, byte(r0 & 0xFF)}) r0++ } cases := map[string]struct { b [][]byte flexible bool mtu uint16 res [][]byte }{ "flexible NilPayload": { b: [][]byte{nil}, flexible: true, mtu: 100, res: [][]byte{}, }, "flexible SmallMTU": { b: [][]byte{{0x00, 0x00}}, flexible: true, mtu: 1, res: [][]byte{}, }, "flexible OnePacket": { b: [][]byte{{0x01, 0x02}}, flexible: true, mtu: 10, res: [][]byte{ {0x9C, rands[0][0], rands[0][1], 0x01, 0x02}, }, }, "flexible TwoPackets": { b: [][]byte{{0x01, 0x02}}, flexible: true, mtu: 4, res: [][]byte{ {0x98, rands[0][0], rands[0][1], 0x01}, {0x94, rands[0][0], rands[0][1], 0x02}, }, }, "flexible ThreePackets": { b: [][]byte{{0x01, 0x02, 0x03}}, flexible: true, mtu: 4, res: [][]byte{ {0x98, rands[0][0], rands[0][1], 0x01}, {0x90, rands[0][0], rands[0][1], 0x02}, {0x94, rands[0][0], rands[0][1], 0x03}, }, }, "flexible TwoFramesFourPackets": { b: [][]byte{{0x01, 0x02, 0x03}, {0x04}}, flexible: true, mtu: 5, res: [][]byte{ {0x98, rands[0][0], rands[0][1], 0x01, 0x02}, {0x94, rands[0][0], rands[0][1], 0x03}, {0x9C, rands[1][0], rands[1][1], 0x04}, }, }, "non-flexible NilPayload": { b: [][]byte{nil}, mtu: 100, res: [][]byte{}, }, "non-flexible SmallMTU": { b: [][]byte{{0x82, 0x49, 0x83, 0x42, 0x0, 0x77, 0xf0, 0x32, 0x34}}, mtu: 1, res: [][]byte{}, }, "non-flexible OnePacket key frame": { b: [][]byte{{0x82, 0x49, 0x83, 0x42, 0x0, 0x77, 0xf0, 0x32, 0x34}}, mtu: 20, res: [][]byte{{ 0x8f, 0xa1, 0xf4, 0x18, 0x07, 0x80, 0x03, 0x24, 0x01, 0x14, 0x01, 0x82, 0x49, 0x83, 0x42, 0x00, 0x77, 0xf0, 0x32, 0x34, }}, }, "non-flexible TwoPackets key frame": { b: [][]byte{{0x82, 0x49, 0x83, 0x42, 0x0, 0x77, 0xf0, 0x32, 0x34}}, mtu: 12, res: [][]byte{ { 0x8b, 0xa1, 0xf4, 0x18, 0x07, 0x80, 0x03, 0x24, 0x01, 0x14, 0x01, 0x82, }, { 0x85, 0xa1, 0xf4, 0x49, 0x83, 0x42, 0x00, 0x77, 0xf0, 0x32, 0x34, }, }, }, "non-flexible ThreePackets key frame": { b: [][]byte{{ 0x82, 0x49, 0x83, 0x42, 0x00, 0x77, 0xf0, 0x32, 0x34, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, }}, mtu: 12, res: [][]byte{ { 0x8b, 0xa1, 0xf4, 0x18, 0x07, 0x80, 0x03, 0x24, 0x01, 0x14, 0x01, 0x82, }, { 0x81, 0xa1, 0xf4, 0x49, 0x83, 0x42, 0x00, 0x77, 0xf0, 0x32, 0x34, 0x01, }, { 0x85, 0xa1, 0xf4, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, }, }, }, "non-flexible OnePacket non key frame": { b: [][]byte{{0x86, 0x0, 0x40, 0x92, 0xe1, 0x31, 0x42, 0x8c, 0xc0, 0x40}}, mtu: 20, res: [][]byte{{ 0xcd, 0xa1, 0xf4, 0x86, 0x00, 0x40, 0x92, 0xe1, 0x31, 0x42, 0x8c, 0xc0, 0x40, }}, }, } for name, testCase := range cases { t.Run(name, func(t *testing.T) { pck := VP9Payloader{ FlexibleMode: testCase.flexible, InitialPictureIDFn: func() uint16 { return uint16(rand.New(rand.NewSource(0)).Int31n(0x7FFF)) //nolint:gosec }, } res := [][]byte{} for _, b := range testCase.b { res = append(res, pck.Payload(testCase.mtu, b)...) } assert.Equal(t, testCase.res, res) }) } t.Run("PictureIDOverflow", func(t *testing.T) { pck := VP9Payloader{ FlexibleMode: true, InitialPictureIDFn: func() uint16 { return uint16(rand.New(rand.NewSource(0)).Int31n(0x7FFF)) //nolint:gosec }, } pPrev := VP9Packet{} for i := 0; i < 0x8000; i++ { res := pck.Payload(4, []byte{0x01}) packet := VP9Packet{} _, err := packet.Unmarshal(res[0]) assert.NoError(t, err) if i > 0 { if pPrev.PictureID == 0x7FFF { assert.Equal( t, uint16(0), packet.PictureID, "Picture ID next to 0x7FFF must be 0", ) } else { assert.Equal(t, pPrev.PictureID+1, packet.PictureID, "Picture ID next must be incremented by 1") } } pPrev = packet } }) } func TestVP9IsPartitionHead(t *testing.T) { vp9 := &VP9Packet{} t.Run("SmallPacket", func(t *testing.T) { assert.False(t, vp9.IsPartitionHead(nil), "Small packet should not be the head of a new partition") }) t.Run("NormalPacket", func(t *testing.T) { assert.True( t, vp9.IsPartitionHead([]byte{0x18, 0x00, 0x00}), "VP9 RTP packet with B flag should be head of a new partition", ) assert.False( t, vp9.IsPartitionHead([]byte{0x10, 0x00, 0x00}), "VP9 RTP packet without B flag should not be head of a new partition", ) }) } rtp-1.8.27/depacketizer.go000066400000000000000000000014451512154406600154270ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp // Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload. type Depacketizer interface { // Unmarshal parses the RTP payload and returns media. // Metadata may be stored on the Depacketizer itself Unmarshal(packet []byte) ([]byte, error) // Checks if the packet is at the beginning of a partition. This // should return false if the result could not be determined, in // which case the caller will detect timestamp discontinuities. IsPartitionHead(payload []byte) bool // Checks if the packet is at the end of a partition. This should // return false if the result could not be determined. IsPartitionTail(marker bool, payload []byte) bool } rtp-1.8.27/error.go000066400000000000000000000023361512154406600141060ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "errors" ) var ( errHeaderSizeInsufficient = errors.New("RTP header size insufficient") errHeaderSizeInsufficientForExtension = errors.New("RTP header size insufficient for extension") errTooSmall = errors.New("buffer too small") errHeaderExtensionsNotEnabled = errors.New("h.Extension not enabled") errHeaderExtensionNotFound = errors.New("extension not found") errRFC8285OneByteHeaderIDRange = errors.New( "header extension id must be between 1 and 14 for RFC 5285 one byte extensions", ) errRFC8285OneByteHeaderSize = errors.New( "header extension payload must be 16bytes or less for RFC 5285 one byte extensions", ) errRFC8285TwoByteHeaderIDRange = errors.New( "header extension id must be between 1 and 255 for RFC 5285 two byte extensions", ) errRFC8285TwoByteHeaderSize = errors.New( "header extension payload must be 255bytes or less for RFC 5285 two byte extensions", ) errRFC3550HeaderIDRange = errors.New("header extension id must be 0 for non-RFC 5285 extensions") errInvalidRTPPadding = errors.New("invalid RTP padding") ) rtp-1.8.27/examples/000077500000000000000000000000001512154406600142405ustar00rootroot00000000000000rtp-1.8.27/examples/send-h264/000077500000000000000000000000001512154406600156525ustar00rootroot00000000000000rtp-1.8.27/examples/send-h264/README.md000066400000000000000000000016201512154406600171300ustar00rootroot00000000000000# send-h264 send-h264 shows how to packetize H264 and send across the network via RTP ## Instructions ### Create H264 Annex-B file named `output.h264` ``` ffmpeg -f lavfi -i testsrc=duration=30:size=1280x720:rate=30 -c:v libx264 -bsf:v h264_mp4toannexb -b:v 2M -max_delay 0 -bf 0 -pix_fmt yuv420p output.h264 ``` ### Run send-h264 Make sure your `output.h264` is in the `send-h264` directory. Then run the example. ``` go run . ``` This results in output like ``` Received seq_num 31478 from 127.0.0.1:61169 Received seq_num 31479 from 127.0.0.1:61169 Received seq_num 31480 from 127.0.0.1:61169 Received seq_num 31481 from 127.0.0.1:61169 Received seq_num 31482 from 127.0.0.1:61169 Received seq_num 31483 from 127.0.0.1:61169 ``` The receiver will print metadata about the RTP packets. We send a RTP packet every 100 milliseconds, this is an arbitrary time. A real application would pace them correctly. rtp-1.8.27/examples/send-h264/main.go000066400000000000000000000031371512154406600171310ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT // send-h264 demonstrates how to send and receieve RTP packets over the network. package main import ( "fmt" "log" "net" "os" "time" "github.com/pion/rtp" "github.com/pion/rtp/codecs" ) func readThread() int { conn, err := net.ListenUDP("udp", &(net.UDPAddr{ Port: 0, IP: net.ParseIP("0.0.0.0"), })) if err != nil { panic(err) } go func() { buf := make([]byte, 1024) var pkt rtp.Packet for { n, remoteAddr, err := conn.ReadFromUDP(buf) if err != nil { panic(err) } if err = pkt.Unmarshal(buf[:n]); err != nil { panic(err) } fmt.Printf("Received seq_num %d from %s\n", pkt.SequenceNumber, remoteAddr) } }() if udpAddr, ok := conn.LocalAddr().(*net.UDPAddr); ok { return udpAddr.Port } return 0 } func main() { data, err := os.ReadFile("output.h264") if err != nil { log.Fatal(err) } listeningPort := readThread() conn, err := net.DialUDP("udp", nil, &(net.UDPAddr{ Port: listeningPort, IP: net.ParseIP("0.0.0.0"), })) if err != nil { panic(err) } packetizer := rtp.NewPacketizer(100, 98, 0x1234ABCD, &codecs.H264Payloader{}, rtp.NewRandomSequencer(), 90000) // A real application would call Packetize in a loop with proper timing. // For this demo we aren't actually timing things packets := packetizer.Packetize(data, 0) for i := range packets { marshaled, err := packets[i].Marshal() if err != nil { panic(err) } if _, err = conn.Write(marshaled); err != nil { panic(err) } time.Sleep(time.Millisecond * 100) } } rtp-1.8.27/go.mod000066400000000000000000000004121512154406600135250ustar00rootroot00000000000000module github.com/pion/rtp go 1.21 require ( github.com/pion/randutil v0.1.0 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 ) rtp-1.8.27/go.sum000066400000000000000000000020321512154406600135520ustar00rootroot00000000000000github.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/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= 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= rtp-1.8.27/header_extension.go000066400000000000000000000221041512154406600162740ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "bytes" "encoding/binary" "fmt" "io" ) const ( headerExtensionIDReserved = 0xF ) // HeaderExtension represents an RTP extension header. type HeaderExtension interface { Set(id uint8, payload []byte) error GetIDs() []uint8 Get(id uint8) []byte Del(id uint8) error Unmarshal(buf []byte) (int, error) Marshal() ([]byte, error) MarshalTo(buf []byte) (int, error) MarshalSize() int } // OneByteHeaderExtension is an RFC8285 one-byte header extension. type OneByteHeaderExtension struct { payload []byte } // Set sets the extension payload for the specified ID. func (e *OneByteHeaderExtension) Set(id uint8, buf []byte) error { if err := headerExtensionCheck(ExtensionProfileOneByte, id, buf); err != nil { return err } for n := 4; n < len(e.payload); { if e.payload[n] == 0x00 { // padding n++ continue } extid := e.payload[n] >> 4 payloadLen := int(e.payload[n]&^0xF0 + 1) n++ if extid == id { e.payload = append(e.payload[:n], append(buf, e.payload[n+payloadLen:]...)...) return nil } n += payloadLen } if len(e.payload) == 0 && !bytes.HasPrefix(buf, []byte{0xBE, 0xDE, 0x00, 0x00}) { e.payload = []byte{0xBE, 0xDE, 0x00, 0x00} } e.payload = append(e.payload, (id<<4 | uint8(len(buf)-1))) // nolint: gosec // G115 e.payload = append(e.payload, buf...) binary.BigEndian.PutUint16(e.payload[2:4], binary.BigEndian.Uint16(e.payload[2:4])+1) return nil } // GetIDs returns the available IDs. func (e *OneByteHeaderExtension) GetIDs() []uint8 { ids := make([]uint8, 0, binary.BigEndian.Uint16(e.payload[2:4])) for n := 4; n < len(e.payload); { if e.payload[n] == 0x00 { // padding n++ continue } extid := e.payload[n] >> 4 payloadLen := int(e.payload[n]&^0xF0 + 1) n++ if extid == headerExtensionIDReserved { break } ids = append(ids, extid) n += payloadLen } return ids } // Get returns the payload of the extension with the given ID. func (e *OneByteHeaderExtension) Get(id uint8) []byte { for n := 4; n < len(e.payload); { if e.payload[n] == 0x00 { // padding n++ continue } extid := e.payload[n] >> 4 payloadLen := int(e.payload[n]&^0xF0 + 1) n++ if extid == id { return e.payload[n : n+payloadLen] } n += payloadLen } return nil } // Del deletes the extension with the specified ID. func (e *OneByteHeaderExtension) Del(id uint8) error { for n := 4; n < len(e.payload); { if e.payload[n] == 0x00 { // padding n++ continue } extid := e.payload[n] >> 4 payloadLen := int(e.payload[n]&^0xF0 + 1) if extid == id { e.payload = append(e.payload[:n], e.payload[n+1+payloadLen:]...) return nil } n += payloadLen + 1 } return errHeaderExtensionNotFound } // Unmarshal parses the extension payload. func (e *OneByteHeaderExtension) Unmarshal(buf []byte) (int, error) { profile := binary.BigEndian.Uint16(buf[0:2]) if profile != ExtensionProfileOneByte { return 0, fmt.Errorf("%w actual(%x)", errHeaderExtensionNotFound, buf[0:2]) } e.payload = buf return len(buf), nil } // Marshal returns the extension payload. func (e OneByteHeaderExtension) Marshal() ([]byte, error) { return e.payload, nil } // MarshalTo writes the extension payload to the given buffer. func (e OneByteHeaderExtension) MarshalTo(buf []byte) (int, error) { size := e.MarshalSize() if size > len(buf) { return 0, io.ErrShortBuffer } return copy(buf, e.payload), nil } // MarshalSize returns the size of the extension payload. func (e OneByteHeaderExtension) MarshalSize() int { return len(e.payload) } // TwoByteHeaderExtension is an RFC8285 two-byte header extension. type TwoByteHeaderExtension struct { payload []byte } // Set sets the extension payload for the specified ID. func (e *TwoByteHeaderExtension) Set(id uint8, buf []byte) error { if err := headerExtensionCheck(ExtensionProfileTwoByte, id, buf); err != nil { return err } for n := 4; n < len(e.payload); { if e.payload[n] == 0x00 { // padding n++ continue } extid := e.payload[n] n++ payloadLen := int(e.payload[n]) n++ if extid == id { e.payload = append(e.payload[:n], append(buf, e.payload[n+payloadLen:]...)...) return nil } n += payloadLen } if len(e.payload) == 0 && !bytes.HasPrefix(buf, []byte{0x10, 0x00, 0x00, 0x00}) { e.payload = []byte{0x10, 0x00, 0x00, 0x00} } e.payload = append(e.payload, id, uint8(len(buf))) // nolint: gosec // G115 e.payload = append(e.payload, buf...) binary.BigEndian.PutUint16(e.payload[2:4], binary.BigEndian.Uint16(e.payload[2:4])+1) return nil } // GetIDs returns the available IDs. func (e *TwoByteHeaderExtension) GetIDs() []uint8 { ids := make([]uint8, 0, binary.BigEndian.Uint16(e.payload[2:4])) for n := 4; n < len(e.payload); { if e.payload[n] == 0x00 { // padding n++ continue } extid := e.payload[n] n++ payloadLen := int(e.payload[n]) n++ ids = append(ids, extid) n += payloadLen } return ids } // Get returns the payload of the extension with the given ID. func (e *TwoByteHeaderExtension) Get(id uint8) []byte { for n := 4; n < len(e.payload); { if e.payload[n] == 0x00 { // padding n++ continue } extid := e.payload[n] n++ payloadLen := int(e.payload[n]) n++ if extid == id { return e.payload[n : n+payloadLen] } n += payloadLen } return nil } // Del deletes the extension with the specified ID. func (e *TwoByteHeaderExtension) Del(id uint8) error { for n := 4; n < len(e.payload); { if e.payload[n] == 0x00 { // padding n++ continue } extid := e.payload[n] payloadLen := int(e.payload[n+1]) if extid == id { e.payload = append(e.payload[:n], e.payload[n+2+payloadLen:]...) return nil } n += payloadLen + 2 } return errHeaderExtensionNotFound } // Unmarshal parses the extension payload. func (e *TwoByteHeaderExtension) Unmarshal(buf []byte) (int, error) { profile := binary.BigEndian.Uint16(buf[0:2]) if profile != ExtensionProfileTwoByte { return 0, fmt.Errorf("%w actual(%x)", errHeaderExtensionNotFound, buf[0:2]) } e.payload = buf return len(buf), nil } // Marshal returns the extension payload. func (e TwoByteHeaderExtension) Marshal() ([]byte, error) { return e.payload, nil } // MarshalTo marshals the extension to the given buffer. func (e TwoByteHeaderExtension) MarshalTo(buf []byte) (int, error) { size := e.MarshalSize() if size > len(buf) { return 0, io.ErrShortBuffer } return copy(buf, e.payload), nil } // MarshalSize returns the size of the extension payload. func (e TwoByteHeaderExtension) MarshalSize() int { return len(e.payload) } // RawExtension represents an RFC3550 header extension. type RawExtension struct { payload []byte } // Set sets the extension payload for the specified ID. func (e *RawExtension) Set(id uint8, payload []byte) error { if err := headerExtensionCheck(0, id, payload); err != nil { return err } e.payload = payload return nil } // GetIDs returns the available IDs. func (e *RawExtension) GetIDs() []uint8 { return []uint8{0} } // Get returns the payload of the extension with the given ID. func (e *RawExtension) Get(id uint8) []byte { if id == 0 { return e.payload } return nil } // Del deletes the extension with the specified ID. func (e *RawExtension) Del(id uint8) error { if id == 0 { e.payload = nil return nil } return fmt.Errorf("%w actual(%d)", errRFC3550HeaderIDRange, id) } // Unmarshal parses the extension from the given buffer. func (e *RawExtension) Unmarshal(buf []byte) (int, error) { profile := binary.BigEndian.Uint16(buf[0:2]) if profile == ExtensionProfileOneByte || profile == ExtensionProfileTwoByte { return 0, fmt.Errorf("%w actual(%x)", errHeaderExtensionNotFound, buf[0:2]) } e.payload = buf return len(buf), nil } // Marshal returns the raw extension payload. func (e RawExtension) Marshal() ([]byte, error) { return e.payload, nil } // MarshalTo marshals the extension to the given buffer. func (e RawExtension) MarshalTo(buf []byte) (int, error) { size := e.MarshalSize() if size > len(buf) { return 0, io.ErrShortBuffer } return copy(buf, e.payload), nil } // MarshalSize returns the size of the extension when marshaled. func (e RawExtension) MarshalSize() int { return len(e.payload) } // Assert that id + value is valid for give Header Extension Profile. func headerExtensionCheck(extensionProfile uint16, id uint8, payload []byte) error { switch extensionProfile { // RFC 8285 RTP One Byte Header Extension case ExtensionProfileOneByte: if id < 1 || id > 14 { return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderIDRange, id) } if len(payload) > 16 { return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderSize, len(payload)) } // RFC 8285 RTP Two Byte Header Extension case ExtensionProfileTwoByte: if id < 1 { return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderIDRange, id) } if len(payload) > 255 { return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderSize, len(payload)) } default: // RFC3550 Extension if id != 0 { return fmt.Errorf("%w actual(%d)", errRFC3550HeaderIDRange, id) } } return nil } rtp-1.8.27/header_extension_test.go000066400000000000000000000267031512154406600173440ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "testing" "github.com/stretchr/testify/assert" ) func TestHeaderExtension_RFC8285OneByteExtension(t *testing.T) { p := &OneByteHeaderExtension{} rawPkt := []byte{ 0xBE, 0xDE, 0x00, 0x01, 0x50, 0xAA, 0x00, 0x00, 0x98, 0x36, 0xbe, 0x88, 0x9e, } _, err := p.Unmarshal(rawPkt) assert.NoError(t, err, "Unmarshal err for valid extension") dstData, _ := p.Marshal() assert.Equal(t, rawPkt, dstData) } func TestHeaderExtension_RFC8285OneByteTwoExtensionOfTwoBytes(t *testing.T) { ext := &OneByteHeaderExtension{} // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0xBE | 0xDE | length=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | L=0 | data | ID | L=0 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0xBE, 0xDE, 0x00, 0x01, 0x10, 0xAA, 0x20, 0xBB, } _, err := ext.Unmarshal(rawPkt) assert.NoError(t, err, "Unmarshal err for valid extension") ext1 := ext.Get(1) ext1Expect := []byte{0xAA} assert.Equal(t, ext1Expect, ext1, "Extension has incorrect data") ext2 := ext.Get(2) ext2Expect := []byte{0xBB} assert.Equal(t, ext2Expect, ext2, "Extension has incorrect data") dstData, _ := ext.Marshal() assert.Equal(t, rawPkt, dstData) } func TestHeaderExtension_RFC8285OneByteMultipleExtensionsWithPadding(t *testing.T) { ext := &OneByteHeaderExtension{} // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0xBE | 0xDE | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | L=0 | data | ID | L=1 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data | 0 (pad) | 0 (pad) | ID | L=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0xBE, 0xDE, 0x00, 0x03, 0x10, 0xAA, 0x21, 0xBB, 0xBB, 0x00, 0x00, 0x33, 0xCC, 0xCC, 0xCC, 0xCC, } _, err := ext.Unmarshal(rawPkt) assert.NoError(t, err, "Unmarshal err for valid extension") ext1 := ext.Get(1) ext1Expect := []byte{0xAA} assert.Equal(t, ext1Expect, ext1, "Extension has incorrect data") ext2 := ext.Get(2) ext2Expect := []byte{0xBB, 0xBB} assert.Equal(t, ext2Expect, ext2, "Extension has incorrect data") ext3 := ext.Get(3) ext3Expect := []byte{0xCC, 0xCC, 0xCC, 0xCC} assert.Equal(t, ext3Expect, ext3, "Extension has incorrect data") dstBuf := map[string][]byte{ "CleanBuffer": make([]byte, 1000), "DirtyBuffer": make([]byte, 1000), } for i := range dstBuf["DirtyBuffer"] { dstBuf["DirtyBuffer"][i] = 0xFF } for name, buf := range dstBuf { buf := buf t.Run(name, func(t *testing.T) { n, err := ext.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, rawPkt, buf[:n]) }) } } func TestHeaderExtension_RFC8285TwoByteExtension(t *testing.T) { ext := &TwoByteHeaderExtension{} rawPkt := []byte{ 0x10, 0x00, 0x00, 0x07, 0x05, 0x18, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, } _, err := ext.Unmarshal(rawPkt) assert.NoError(t, err, "Unmarshal err for valid extension") dstData, err := ext.Marshal() assert.NoError(t, err) assert.Equal(t, rawPkt, dstData) _, err = ext.MarshalTo(dstData) assert.NoError(t, err) assert.Equal(t, rawPkt, dstData) } func TestHeaderExtension_RFC8285TwoByteMultipleExtensionsWithPadding(t *testing.T) { ext := &TwoByteHeaderExtension{} // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0x10 | 0x00 | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID=1 | L=0 | ID=2 | L=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | 0 (pad) | ID=3 | L=4 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x10, 0x00, 0x00, 0x03, 0x01, 0x00, 0x02, 0x01, 0xBB, 0x00, 0x03, 0x04, 0xCC, 0xCC, 0xCC, 0xCC, } _, err := ext.Unmarshal(rawPkt) assert.NoError(t, err, "Unmarshal err for valid extension") ext1 := ext.Get(1) ext1Expect := []byte{} assert.Equal(t, ext1Expect, ext1, "Extension has incorrect data") ext2 := ext.Get(2) ext2Expect := []byte{0xBB} assert.Equal(t, ext2Expect, ext2, "Extension has incorrect data") ext3 := ext.Get(3) ext3Expect := []byte{0xCC, 0xCC, 0xCC, 0xCC} assert.Equal(t, ext3Expect, ext3, "Extension has incorrect data") } func TestHeaderExtension_RFC8285TwoByteMultipleExtensionsWithLargeExtension(t *testing.T) { ext := &TwoByteHeaderExtension{} // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0x10 | 0x00 | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID=1 | L=0 | ID=2 | L=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | ID=3 | L=17 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x10, 0x00, 0x00, 0x06, 0x01, 0x00, 0x02, 0x01, 0xBB, 0x03, 0x11, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, } _, err := ext.Unmarshal(rawPkt) assert.NoError(t, err, "Unmarshal err for valid extension") ext1 := ext.Get(1) ext1Expect := []byte{} assert.Equal(t, ext1Expect, ext1, "Extension has incorrect data") ext2 := ext.Get(2) ext2Expect := []byte{0xBB} assert.Equal(t, ext2Expect, ext2, "Extension has incorrect data") ext3 := ext.Get(3) ext3Expect := []byte{ 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, } assert.Equal(t, ext3Expect, ext3, "Extension has incorrect data") dstData, _ := ext.Marshal() assert.Equal(t, rawPkt, dstData) } func TestHeaderExtension_Invalid(t *testing.T) { oneByteExt := &OneByteHeaderExtension{} twoByteExt := &TwoByteHeaderExtension{} // Invalid extension IDs assert.Error(t, oneByteExt.Set(0, nil)) assert.Error(t, twoByteExt.Set(0, nil)) assert.Error(t, oneByteExt.Set(15, nil)) // Extension too large assert.Error(t, oneByteExt.Set(10, make([]byte, 255))) assert.Error(t, twoByteExt.Set(10, make([]byte, 500))) } func TestHeaderExtension_RFC8285OneByteDelExtension(t *testing.T) { ext := &OneByteHeaderExtension{} _, err := ext.Unmarshal([]byte{0xBE, 0xDE, 0x00, 0x00}) assert.NoError(t, err, "Unmarshal err for valid extension") assert.NoError(t, ext.Set(1, []byte{0xBB}), "Set err for valid extension") assert.NotNil(t, ext.Get(1), "Extension should exist") assert.NoError(t, ext.Del(1), "Should successfully delete extension") assert.Nil(t, ext.Get(1), "Extension should not") assert.Error(t, ext.Del(1), "Should return error when deleting extension that doesnt exist") } func TestHeaderExtension_GetIds(t *testing.T) { oneByteExt := &OneByteHeaderExtension{} assert.NoError(t, oneByteExt.Set(1, []byte{0xBB})) assert.NoError(t, oneByteExt.Set(3, []byte{0xAA})) assert.NoError(t, oneByteExt.Set(5, []byte{0xFF})) assert.Equal(t, oneByteExt.GetIDs(), []uint8{1, 3, 5}) twoByteExt := &TwoByteHeaderExtension{} assert.NoError(t, twoByteExt.Set(1, []byte{0xBB})) assert.NoError(t, twoByteExt.Set(3, []byte{0xAA})) assert.NoError(t, twoByteExt.Set(5, []byte{0xFF})) assert.Equal(t, twoByteExt.GetIDs(), []uint8{1, 3, 5}) } func TestHeaderExtension_RFC8285TwoByteDelExtension(t *testing.T) { ext := &TwoByteHeaderExtension{} _, err := ext.Unmarshal([]byte{0x10, 0x00, 0x00, 0x00}) assert.NoError(t, err, "Unmarshal err for valid extension") assert.NoError(t, ext.Set(1, []byte{0xBB}), "Set err for valid extension") extExtension := ext.Get(1) assert.NotNil(t, extExtension, "Extension should exist") assert.NoError(t, ext.Del(1), "Should successfully delete extension") extExtension = ext.Get(1) assert.Nil(t, extExtension, "Extension should exist") assert.Error(t, ext.Del(1), "Should return error when deleting extension that doesnt exist") } func TestHeaderExtension_RFC8285OneByteExtensionRewrite(t *testing.T) { ext := &OneByteHeaderExtension{} assert.NoError(t, ext.Set(1, []byte{0x01, 0x02, 0x03})) res, err := ext.Marshal() assert.NoError(t, err) assert.Equal(t, res, []byte{0xBE, 0xDE, 0x00, 0x01, 0x12, 0x01, 0x02, 0x03}) assert.NoError(t, ext.Set(1, []byte{0x04, 0x05, 0x06})) res, err = ext.Marshal() assert.NoError(t, err) assert.Equal(t, res, []byte{0xBE, 0xDE, 0x00, 0x01, 0x12, 0x04, 0x05, 0x06}) assert.NoError(t, ext.Set(3, []byte{0x07, 0x08, 0x09})) res, err = ext.Marshal() assert.NoError(t, err) assert.Equal(t, res, []byte{0xBE, 0xDE, 0x00, 0x02, 0x12, 0x04, 0x05, 0x06, 0x32, 0x07, 0x08, 0x09}) } func TestHeaderExtension_RFC8285TwoByteExtensionRewrite(t *testing.T) { ext := &TwoByteHeaderExtension{} assert.NoError(t, ext.Set(200, []byte{0x01, 0x02, 0x03})) res, err := ext.Marshal() assert.NoError(t, err) assert.Equal(t, res, []byte{0x10, 0x00, 0x00, 0x01, 0xc8, 0x03, 0x01, 0x02, 0x03}) assert.NoError(t, ext.Set(200, []byte{0x04, 0x05, 0x06})) res, err = ext.Marshal() assert.NoError(t, err) assert.Equal(t, res, []byte{0x10, 0x00, 0x00, 0x01, 0xc8, 0x03, 0x04, 0x05, 0x06}) assert.NoError(t, ext.Set(50, []byte{0x07, 0x08, 0x09})) res, err = ext.Marshal() assert.NoError(t, err) assert.Equal(t, res, []byte{0x10, 0x00, 0x00, 0x02, 0xc8, 0x03, 0x04, 0x05, 0x06, 0x32, 0x03, 0x07, 0x08, 0x09}) } func TestHeaderExtension_Raw(t *testing.T) { ext := &RawExtension{} expectedPayload := []byte{0xBE, 0xEF} assert.Error(t, ext.Set(5, expectedPayload)) assert.NoError(t, ext.Set(0, expectedPayload)) marshaled, err := ext.Marshal() assert.NoError(t, err) assert.Equal(t, marshaled, expectedPayload) _, err = ext.Unmarshal(marshaled) assert.NoError(t, err) _, err = ext.MarshalTo(nil) assert.Error(t, err) _, err = ext.MarshalTo(marshaled) assert.NoError(t, err) assert.Equal(t, ext.GetIDs(), []uint8{0}) assert.NoError(t, ext.Del(0)) assert.Nil(t, ext.Get(0)) } rtp-1.8.27/packet.go000066400000000000000000000430241512154406600142230ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "encoding/binary" "fmt" "io" ) // Extension RTP Header extension. type Extension struct { id uint8 payload []byte } // Header represents an RTP packet header. type Header struct { Version uint8 Padding bool Extension bool Marker bool PayloadType uint8 SequenceNumber uint16 Timestamp uint32 SSRC uint32 CSRC []uint32 ExtensionProfile uint16 Extensions []Extension // PaddingLength is the length of the padding in bytes. It is not part of the RTP header // (it is sent in the last byte of RTP packet padding), but logically it belongs here. PaddingSize byte // Deprecated: will be removed in a future version. PayloadOffset int } // Packet represents an RTP Packet. type Packet struct { Header Payload []byte PaddingSize byte // Deprecated: will be removed in a future version. Use Header.PaddingSize instead. // Deprecated: will be removed in a future version. Raw []byte // Please do not add any new field directly to Packet struct unless you know that it is safe. // pion internally passes Header and Payload separately, what causes bugs like // https://github.com/pion/webrtc/issues/2403 . } const ( // ExtensionProfileOneByte is the RTP One Byte Header Extension Profile, defined in RFC 8285. ExtensionProfileOneByte = 0xBEDE // ExtensionProfileTwoByte is the RTP Two Byte Header Extension Profile, defined in RFC 8285. ExtensionProfileTwoByte = 0x1000 // CryptexProfileOneByte is the Cryptex One Byte Header Extension Profile, defined in RFC 9335. CryptexProfileOneByte = 0xC0DE // CryptexProfileTwoByte is the Cryptex Two Byte Header Extension Profile, defined in RFC 9335. CryptexProfileTwoByte = 0xC2DE ) const ( headerLength = 4 versionShift = 6 versionMask = 0x3 paddingShift = 5 paddingMask = 0x1 extensionShift = 4 extensionMask = 0x1 extensionIDReserved = 0xF extensionIDPadding = 0x0 ccMask = 0xF markerShift = 7 markerMask = 0x1 ptMask = 0x7F seqNumOffset = 2 seqNumLength = 2 timestampOffset = 4 timestampLength = 4 ssrcOffset = 8 ssrcLength = 4 csrcOffset = 12 csrcLength = 4 ) // String helps with debugging by printing packet information in a readable way. func (p Packet) String() string { out := "RTP PACKET:\n" out += fmt.Sprintf("\tVersion: %v\n", p.Version) out += fmt.Sprintf("\tMarker: %v\n", p.Marker) out += fmt.Sprintf("\tPayload Type: %d\n", p.PayloadType) out += fmt.Sprintf("\tSequence Number: %d\n", p.SequenceNumber) out += fmt.Sprintf("\tTimestamp: %d\n", p.Timestamp) out += fmt.Sprintf("\tSSRC: %d (%x)\n", p.SSRC, p.SSRC) out += fmt.Sprintf("\tPayload Length: %d\n", len(p.Payload)) return out } // Unmarshal parses the passed byte slice and stores the result in the Header. // It returns the number of bytes read n and any error. func (h *Header) Unmarshal(buf []byte) (n int, err error) { //nolint:gocognit,cyclop if len(buf) < headerLength { return 0, fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(buf), headerLength) } /* * 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|X| CC |M| PT | sequence number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | timestamp | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | synchronization source (SSRC) identifier | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * | contributing source (CSRC) identifiers | * | .... | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ h.Version = buf[0] >> versionShift & versionMask h.Padding = (buf[0] >> paddingShift & paddingMask) > 0 h.Extension = (buf[0] >> extensionShift & extensionMask) > 0 nCSRC := int(buf[0] & ccMask) if cap(h.CSRC) < nCSRC { h.CSRC = make([]uint32, nCSRC) } else { h.CSRC = h.CSRC[:nCSRC] } n = csrcOffset + (nCSRC * csrcLength) if len(buf) < n { return n, fmt.Errorf("size %d < %d: %w", len(buf), n, errHeaderSizeInsufficient) } headerLength := n h.Marker = (buf[1] >> markerShift & markerMask) > 0 h.PayloadType = buf[1] & ptMask h.SequenceNumber = binary.BigEndian.Uint16(buf[seqNumOffset : seqNumOffset+seqNumLength]) h.Timestamp = binary.BigEndian.Uint32(buf[timestampOffset : timestampOffset+timestampLength]) h.SSRC = binary.BigEndian.Uint32(buf[ssrcOffset : ssrcOffset+ssrcLength]) for i := range h.CSRC { offset := csrcOffset + (i * csrcLength) h.CSRC[i] = binary.BigEndian.Uint32(buf[offset:]) } h.Extensions = h.Extensions[:0] if h.Extension { // nolint: nestif if expected := n + 4; len(buf) < expected { return n, fmt.Errorf("size %d < %d: %w", len(buf), expected, errHeaderSizeInsufficientForExtension, ) } h.ExtensionProfile = binary.BigEndian.Uint16(buf[n:]) n += 2 extensionLength := int(binary.BigEndian.Uint16(buf[n:])) * 4 n += 2 extensionEnd := n + extensionLength headerLength = extensionEnd if len(buf) < extensionEnd { return n, fmt.Errorf("size %d < %d: %w", len(buf), extensionEnd, errHeaderSizeInsufficientForExtension) } if h.ExtensionProfile == ExtensionProfileOneByte || h.ExtensionProfile == ExtensionProfileTwoByte { var ( extid uint8 payloadLen int ) for n < extensionEnd { if buf[n] == extensionIDPadding { // padding n++ continue } if h.ExtensionProfile == ExtensionProfileOneByte { extid = buf[n] >> 4 payloadLen = int(buf[n]&^0xF0 + 1) n++ // Stop parsing extensions if we reach the reserved ID or padding with non-zero length if extid == extensionIDReserved || extid == extensionIDPadding { break } } else { extid = buf[n] n++ if extensionEnd <= n { return n, fmt.Errorf("size %d < %d: %w", extensionEnd, n, errHeaderSizeInsufficientForExtension) } payloadLen = int(buf[n]) n++ } if extensionPayloadEnd := n + payloadLen; extensionEnd < extensionPayloadEnd { return n, fmt.Errorf("size %d < %d: %w", extensionEnd, extensionPayloadEnd, errHeaderSizeInsufficientForExtension) } extension := Extension{id: extid, payload: buf[n : n+payloadLen]} h.Extensions = append(h.Extensions, extension) n += payloadLen } } else { // RFC3550 Extension extension := Extension{id: 0, payload: buf[n:extensionEnd]} h.Extensions = append(h.Extensions, extension) } } return headerLength, nil } // Unmarshal parses the passed byte slice and stores the result in the Packet. func (p *Packet) Unmarshal(buf []byte) error { n, err := p.Header.Unmarshal(buf) if err != nil { return err } end := len(buf) if p.Header.Padding { if end <= n { return errTooSmall } p.Header.PaddingSize = buf[end-1] end -= int(p.Header.PaddingSize) } else { p.Header.PaddingSize = 0 } p.PaddingSize = p.Header.PaddingSize if end < n { return errTooSmall } p.Payload = buf[n:end] return nil } // Marshal serializes the header into bytes. func (h Header) Marshal() (buf []byte, err error) { buf = make([]byte, h.MarshalSize()) n, err := h.MarshalTo(buf) if err != nil { return nil, err } return buf[:n], nil } // MarshalTo serializes the header and writes to the buffer. func (h Header) MarshalTo(buf []byte) (n int, err error) { //nolint:cyclop /* * 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|X| CC |M| PT | sequence number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | timestamp | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | synchronization source (SSRC) identifier | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * | contributing source (CSRC) identifiers | * | .... | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ size := h.MarshalSize() if size > len(buf) { return 0, io.ErrShortBuffer } // The first byte contains the version, padding bit, extension bit, // and csrc size. buf[0] = (h.Version << versionShift) | uint8(len(h.CSRC)) // nolint: gosec // G115 if h.Padding { buf[0] |= 1 << paddingShift } if h.Extension { buf[0] |= 1 << extensionShift } // The second byte contains the marker bit and payload type. buf[1] = h.PayloadType if h.Marker { buf[1] |= 1 << markerShift } binary.BigEndian.PutUint16(buf[2:4], h.SequenceNumber) binary.BigEndian.PutUint32(buf[4:8], h.Timestamp) binary.BigEndian.PutUint32(buf[8:12], h.SSRC) n = 12 for _, csrc := range h.CSRC { binary.BigEndian.PutUint32(buf[n:n+4], csrc) n += 4 } if h.Extension { extHeaderPos := n binary.BigEndian.PutUint16(buf[n+0:n+2], h.ExtensionProfile) n += 4 startExtensionsPos := n switch h.ExtensionProfile { // RFC 8285 RTP One Byte Header Extension case ExtensionProfileOneByte: for _, extension := range h.Extensions { buf[n] = extension.id<<4 | (uint8(len(extension.payload)) - 1) // nolint: gosec // G115 n++ n += copy(buf[n:], extension.payload) } // RFC 8285 RTP Two Byte Header Extension case ExtensionProfileTwoByte: for _, extension := range h.Extensions { buf[n] = extension.id n++ buf[n] = uint8(len(extension.payload)) // nolint: gosec // G115 n++ n += copy(buf[n:], extension.payload) } default: // RFC3550 Extension // Zero length extension is valid per the RFC3550 spec // https://www.rfc-editor.org/rfc/rfc3550#section-5.3.1 if len(h.Extensions) > 0 { extlen := len(h.Extensions[0].payload) if extlen%4 != 0 { // the payload must be in 32-bit words. return 0, io.ErrShortBuffer } n += copy(buf[n:], h.Extensions[0].payload) } } // calculate extensions size and round to 4 bytes boundaries extSize := n - startExtensionsPos roundedExtSize := ((extSize + 3) / 4) * 4 // nolint: gosec // G115 false positive binary.BigEndian.PutUint16(buf[extHeaderPos+2:extHeaderPos+4], uint16(roundedExtSize/4)) // add padding to reach 4 bytes boundaries for i := 0; i < roundedExtSize-extSize; i++ { buf[n] = 0 n++ } } return n, nil } // MarshalSize returns the size of the header once marshaled. func (h Header) MarshalSize() int { // NOTE: Be careful to match the MarshalTo() method. size := 12 + (len(h.CSRC) * csrcLength) if h.Extension { extSize := 4 switch h.ExtensionProfile { // RFC 8285 RTP One Byte Header Extension case ExtensionProfileOneByte: for _, extension := range h.Extensions { extSize += 1 + len(extension.payload) } // RFC 8285 RTP Two Byte Header Extension case ExtensionProfileTwoByte: for _, extension := range h.Extensions { extSize += 2 + len(extension.payload) } default: if len(h.Extensions) > 0 { extSize += len(h.Extensions[0].payload) } } // extensions size must have 4 bytes boundaries size += ((extSize + 3) / 4) * 4 } return size } // SetExtension sets an RTP header extension. func (h *Header) SetExtension(id uint8, payload []byte) error { //nolint:gocognit, cyclop if h.Extension { // nolint: nestif if err := headerExtensionCheck(h.ExtensionProfile, id, payload); err != nil { return err } // Update existing if it exists else add new extension for i, extension := range h.Extensions { if extension.id == id { h.Extensions[i].payload = payload return nil } } h.Extensions = append(h.Extensions, Extension{id: id, payload: payload}) return nil } // No existing header extensions h.Extension = true switch payloadLen := len(payload); { case payloadLen <= 16: h.ExtensionProfile = ExtensionProfileOneByte case payloadLen > 16 && payloadLen < 256: h.ExtensionProfile = ExtensionProfileTwoByte } h.Extensions = append(h.Extensions, Extension{id: id, payload: payload}) return nil } // SetExtensionWithProfile sets an RTP header extension and converts Header Extension Profile if needed. func (h *Header) SetExtensionWithProfile(id uint8, payload []byte, intendedProfile uint16) error { if !h.Extension || h.ExtensionProfile == intendedProfile { return h.SetExtension(id, payload) } // Don't mutate the packet if Set is going to fail anyway if err := headerExtensionCheck(intendedProfile, id, payload); err != nil { return err } // If downgrading assert that existing Extensions will work if intendedProfile == ExtensionProfileOneByte { for i := range h.Extensions { if err := headerExtensionCheck(intendedProfile, h.Extensions[i].id, h.Extensions[i].payload); err != nil { return err } } } h.ExtensionProfile = intendedProfile return h.SetExtension(id, payload) } // GetExtensionIDs returns an extension id array. func (h *Header) GetExtensionIDs() []uint8 { if !h.Extension { return nil } if len(h.Extensions) == 0 { return nil } ids := make([]uint8, 0, len(h.Extensions)) for _, extension := range h.Extensions { ids = append(ids, extension.id) } return ids } // GetExtension returns an RTP header extension. func (h *Header) GetExtension(id uint8) []byte { if !h.Extension { return nil } for _, extension := range h.Extensions { if extension.id == id { return extension.payload } } return nil } // DelExtension Removes an RTP Header extension. func (h *Header) DelExtension(id uint8) error { if !h.Extension { return errHeaderExtensionsNotEnabled } for i, extension := range h.Extensions { if extension.id == id { h.Extensions = append(h.Extensions[:i], h.Extensions[i+1:]...) return nil } } return errHeaderExtensionNotFound } // Marshal serializes the packet into bytes. func (p Packet) Marshal() (buf []byte, err error) { buf = make([]byte, p.MarshalSize()) n, err := p.MarshalTo(buf) if err != nil { return nil, err } return buf[:n], nil } // MarshalTo serializes the packet and writes to the buffer. func (p *Packet) MarshalTo(buf []byte) (n int, err error) { if p.Header.Padding && p.paddingSize() == 0 { return 0, errInvalidRTPPadding } n, err = p.Header.MarshalTo(buf) if err != nil { return 0, err } return marshalPayloadAndPaddingTo(buf, n, &p.Header, p.Payload, p.paddingSize()) } func marshalPayloadAndPaddingTo(buf []byte, offset int, header *Header, payload []byte, paddingSize byte, ) (n int, err error) { // Make sure the buffer is large enough to hold the packet. if offset+len(payload)+int(paddingSize) > len(buf) { return 0, io.ErrShortBuffer } m := copy(buf[offset:], payload) if header.Padding { buf[offset+m+int(paddingSize-1)] = paddingSize } return offset + m + int(paddingSize), nil } // MarshalSize returns the size of the packet once marshaled. func (p Packet) MarshalSize() int { return p.Header.MarshalSize() + len(p.Payload) + int(p.paddingSize()) } // Clone returns a deep copy of p. func (p Packet) Clone() *Packet { clone := &Packet{} clone.Header = p.Header.Clone() if p.Payload != nil { clone.Payload = make([]byte, len(p.Payload)) copy(clone.Payload, p.Payload) } clone.PaddingSize = p.PaddingSize return clone } // Clone returns a deep copy h. func (h Header) Clone() Header { clone := h if h.CSRC != nil { clone.CSRC = make([]uint32, len(h.CSRC)) copy(clone.CSRC, h.CSRC) } if h.Extensions != nil { ext := make([]Extension, len(h.Extensions)) for i, e := range h.Extensions { ext[i] = e if e.payload != nil { ext[i].payload = make([]byte, len(e.payload)) copy(ext[i].payload, e.payload) } } clone.Extensions = ext } return clone } func (p *Packet) paddingSize() byte { if p.Header.PaddingSize > 0 { return p.Header.PaddingSize } return p.PaddingSize } // MarshalPacketTo serializes the header and payload into bytes. // Parts of pion code passes RTP header and payload separately, so this function // is provided to help with that. // // Deprecated: this function is a temporary workaround and will be removed in pion/webrtc v5. func MarshalPacketTo(buf []byte, header *Header, payload []byte) (int, error) { n, err := header.MarshalTo(buf) if err != nil { return 0, err } return marshalPayloadAndPaddingTo(buf, n, header, payload, header.PaddingSize) } // PacketMarshalSize returns the size of the header and payload once marshaled. // Parts of pion code passes RTP header and payload separately, so this function // is provided to help with that. // // Deprecated: this function is a temporary workaround and will be removed in pion/webrtc v5. func PacketMarshalSize(header *Header, payload []byte) int { return header.MarshalSize() + len(payload) + int(header.PaddingSize) } // HeaderAndPacketMarshalSize returns the size of the header and full packet once marshaled. // Parts of pion code passes RTP header and payload separately, so this function // is provided to help with that. // // Deprecated: this function is a temporary workaround and will be removed in pion/webrtc v5. func HeaderAndPacketMarshalSize(header *Header, payload []byte) (headerSize int, packetSize int) { headerSize = header.MarshalSize() return headerSize, headerSize + len(payload) + int(header.PaddingSize) } rtp-1.8.27/packet_test.go000066400000000000000000001303431512154406600152630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "fmt" "testing" "github.com/stretchr/testify/assert" ) func TestBasic(t *testing.T) { // nolint:maintidx,cyclop packet := &Packet{} assert.Error(t, packet.Unmarshal([]byte{}), "Unmarshal did not error on zero length packet") assert.ErrorIs(t, packet.Unmarshal([]byte{}), errHeaderSizeInsufficient) rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x9e, } parsedPacket := &Packet{ Header: Header{ Padding: false, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, PaddingSize: 0, }, Payload: rawPkt[20:], } // Unmarshal to the used Packet should work as well. for i := 0; i < 2; i++ { t.Run(fmt.Sprintf("Run%d", i+1), func(t *testing.T) { assert.NoError(t, packet.Unmarshal(rawPkt)) assert.Equal(t, packet, parsedPacket) assert.Equal(t, packet.Header.MarshalSize(), 20, "wrong computed header marshal size") assert.Equal(t, packet.MarshalSize(), len(rawPkt), "wrong computed marshal size") raw, err := packet.Marshal() assert.NoError(t, err) assert.Equal(t, rawPkt, raw) }) } // packet with padding rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x04, } parsedPacket = &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, PaddingSize: 4, }, Payload: rawPkt[20:21], PaddingSize: 4, } assert.NoError(t, packet.Unmarshal(rawPkt)) assert.Equal(t, packet, parsedPacket) // packet with zero padding following packet with non-zero padding rawPkt = []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x9e, } parsedPacket = &Packet{ Header: Header{ Padding: false, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, PaddingSize: 0, }, Payload: rawPkt[20:], } assert.NoError(t, packet.Unmarshal(rawPkt)) assert.Equal(t, packet, parsedPacket) // packet with only padding rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x05, } parsedPacket = &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, PaddingSize: 5, }, Payload: []byte{}, PaddingSize: 5, } assert.NoError(t, packet.Unmarshal(rawPkt)) assert.Equal(t, packet, parsedPacket) assert.Len(t, packet.Payload, 0, "Unmarshal of padding only packet has payload of non-zero length") // packet with excessive padding rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x06, } parsedPacket = &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, PaddingSize: 0, }, Payload: []byte{}, } err := packet.Unmarshal(rawPkt) assert.Error(t, err, "Unmarshal did not error on packet with excessive padding") assert.ErrorIs(t, err, errTooSmall) // marshal packet with padding rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x04, } parsedPacket = &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, PaddingSize: 4, }, Payload: rawPkt[20:21], } buf, err := parsedPacket.Marshal() assert.NoError(t, err) assert.Equal(t, rawPkt, buf) // marshal packet with padding only rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x05, } parsedPacket = &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, PaddingSize: 5, }, Payload: []byte{}, } buf, err = parsedPacket.Marshal() assert.NoError(t, err) assert.Equal(t, rawPkt, buf) // marshal packet with padding only without setting Padding explicitly in Header rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x05, } parsedPacket = &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, Padding: true, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, PaddingSize: 5, }, Payload: []byte{}, } buf, err = parsedPacket.Marshal() assert.NoError(t, err) assert.Equal(t, rawPkt, buf) } func TestExtension(t *testing.T) { packet := &Packet{} missingExtensionPkt := []byte{ 0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, } assert.Error( t, packet.Unmarshal(missingExtensionPkt), "Unmarshal did not error on packet with missing extension data", ) invalidExtensionLengthPkt := []byte{ 0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x99, 0x99, 0x99, 0x99, } assert.Error( t, packet.Unmarshal(invalidExtensionLengthPkt), "Unmarshal did not error on packet with invalid extension length", ) packet = &Packet{ Header: Header{ Extension: true, ExtensionProfile: 3, Extensions: []Extension{ {0, []byte{ 0, }}, }, }, Payload: []byte{}, } _, err := packet.Marshal() assert.Error(t, err, "Marshal did not error on packet with invalid extension length") } func TestRFC8285OneByteExtension(t *testing.T) { packet := &Packet{} rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0x50, 0xAA, 0x00, 0x00, 0x98, 0x36, 0xbe, 0x88, 0x9e, } assert.NoError(t, packet.Unmarshal(rawPkt)) packet = &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {5, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: rawPkt[20:], } dstData, _ := packet.Marshal() assert.Equal(t, rawPkt, dstData) } func TestRFC8285OneByteTwoExtensionOfTwoBytes(t *testing.T) { packet := &Packet{} // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0xBE | 0xDE | length=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | L=0 | data | ID | L=0 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0x10, 0xAA, 0x20, 0xBB, // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } assert.NoError(t, packet.Unmarshal(rawPkt)) ext1 := packet.GetExtension(1) ext1Expect := []byte{0xAA} assert.Equal(t, ext1Expect, ext1, "Extension has incorrect data") ext2 := packet.GetExtension(2) ext2Expect := []byte{0xBB} assert.Equal(t, ext2Expect, ext2, "Extension has incorrect data") // Test Marshal packet = &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, {2, []byte{ 0xBB, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: rawPkt[20:], } dstData, _ := packet.Marshal() assert.Equal(t, rawPkt, dstData) } func TestRFC8285OneByteMultipleExtensionsWithPadding(t *testing.T) { packet := &Packet{} // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0xBE | 0xDE | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | L=0 | data | ID | L=1 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data | 0 (pad) | 0 (pad) | ID | L=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x03, 0x10, 0xAA, 0x21, 0xBB, 0xBB, 0x00, 0x00, 0x33, 0xCC, 0xCC, 0xCC, 0xCC, // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } assert.NoError(t, packet.Unmarshal(rawPkt)) ext1 := packet.GetExtension(1) ext1Expect := []byte{0xAA} assert.Equal(t, ext1Expect, ext1, "Extension has incorrect data") ext2 := packet.GetExtension(2) ext2Expect := []byte{0xBB, 0xBB} assert.Equal(t, ext2Expect, ext2, "Extension has incorrect data") ext3 := packet.GetExtension(3) ext3Expect := []byte{0xCC, 0xCC, 0xCC, 0xCC} assert.Equal(t, ext3Expect, ext3, "Extension has incorrect data") rawPktReMarshal := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x03, 0x10, 0xAA, 0x21, 0xBB, 0xBB, 0x33, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00, // padding is moved to the end by re-marshaling // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } dstBuf := map[string][]byte{ "CleanBuffer": make([]byte, 1000), "DirtyBuffer": make([]byte, 1000), } for i := range dstBuf["DirtyBuffer"] { dstBuf["DirtyBuffer"][i] = 0xFF } for name, buf := range dstBuf { buf := buf t.Run(name, func(t *testing.T) { n, err := packet.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, rawPktReMarshal, buf[:n]) }) } } func TestRFC8285OneByteMultipleExtensions(t *testing.T) { // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0xBE | 0xDE | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID=1 | L=0 | data | ID=2 | L=1 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data | ID=3 | L=3 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x03, 0x10, 0xAA, 0x21, 0xBB, 0xBB, 0x33, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00, // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, {2, []byte{ 0xBB, 0xBB, }}, {3, []byte{ 0xCC, 0xCC, 0xCC, 0xCC, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: rawPkt[28:], } dstData, _ := packet.Marshal() assert.Equal(t, rawPkt, dstData) } func TestRFC8285TwoByteExtension(t *testing.T) { packet := &Packet{} rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x10, 0x00, 0x00, 0x07, 0x05, 0x18, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x98, 0x36, 0xbe, 0x88, 0x9e, } assert.NoError(t, packet.Unmarshal(rawPkt)) packet = &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0x1000, Extensions: []Extension{ {5, []byte{ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: rawPkt[44:], } dstData, _ := packet.Marshal() assert.Equal(t, rawPkt, dstData) } func TestRFC8285TwoByteMultipleExtensionsWithPadding(t *testing.T) { packet := &Packet{} // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0x10 | 0x00 | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID=1 | L=0 | ID=2 | L=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | 0 (pad) | ID=3 | L=4 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x10, 0x00, 0x00, 0x03, 0x01, 0x00, 0x02, 0x01, 0xBB, 0x00, 0x03, 0x04, 0xCC, 0xCC, 0xCC, 0xCC, 0x98, 0x36, 0xbe, 0x88, 0x9e, } assert.NoError(t, packet.Unmarshal(rawPkt)) ext1 := packet.GetExtension(1) ext1Expect := []byte{} assert.Equal(t, ext1Expect, ext1, "Extension has incorrect data") ext2 := packet.GetExtension(2) ext2Expect := []byte{0xBB} assert.Equal(t, ext2Expect, ext2, "Extension has incorrect data") ext3 := packet.GetExtension(3) ext3Expect := []byte{0xCC, 0xCC, 0xCC, 0xCC} assert.Equal(t, ext3Expect, ext3, "Extension has incorrect data") rawPktReMarshal := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x10, 0x00, 0x00, 0x03, 0x01, 0x00, 0x02, 0x01, 0xBB, 0x03, 0x04, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, // padding is moved to the end by re-marshaling // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } dstBuf := map[string][]byte{ "CleanBuffer": make([]byte, 1000), "DirtyBuffer": make([]byte, 1000), } for i := range dstBuf["DirtyBuffer"] { dstBuf["DirtyBuffer"][i] = 0xFF } for name, buf := range dstBuf { buf := buf t.Run(name, func(t *testing.T) { n, err := packet.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, rawPktReMarshal, buf[:n]) }) } } func TestRFC8285TwoByteMultipleExtensionsWithLargeExtension(t *testing.T) { // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0x10 | 0x00 | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID=1 | L=0 | ID=2 | L=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | ID=3 | L=17 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x10, 0x00, 0x00, 0x06, 0x01, 0x00, 0x02, 0x01, 0xBB, 0x03, 0x11, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0x1000, Extensions: []Extension{ {1, []byte{}}, {2, []byte{ 0xBB, }}, {3, []byte{ 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: rawPkt[40:], } dstData, _ := packet.Marshal() assert.Equal(t, rawPkt, dstData) } func TestRFC8285GetExtensionReturnsNilWhenExtensionsDisabled(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } assert.Nil(t, packet.GetExtension(1), "Should return nil on GetExtension when h.Extension: false") } func TestRFC8285DelExtension(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } assert.NotNil(t, packet.GetExtension(1), "Extension should exist") assert.NoError(t, packet.DelExtension(1), "Should successfully delete extension") assert.Nil(t, packet.GetExtension(1), "Extension should not exist") assert.Error(t, packet.DelExtension(1), "Should return error when deleting extension that doesnt exist") } func TestRFC8285GetExtensionIDs(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, {2, []byte{ 0xBB, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } ids := packet.GetExtensionIDs() assert.NotNil(t, ids, "Extension should exist") assert.Len(t, ids, len(packet.Extensions), "The number of IDs should be equal to the number of extensions") for _, id := range ids { ext := packet.GetExtension(id) assert.NotNil(t, ext, "Extension should exist") } } func TestRFC8285GetExtensionIDsReturnsErrorWhenExtensionsDisabled(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } assert.Nil(t, packet.GetExtensionIDs(), "Should return nil on GetExtensionIDs when h.Extensions is nil") } func TestRFC8285DelExtensionReturnsErrorWhenExtensionsDisabled(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } assert.Error( t, packet.DelExtension(1), "DelExtension did not error on h.Extension: false", ) } func TestRFC8285OneByteSetExtensionShouldEnableExensionsWhenAdding(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } extension := []byte{0xAA, 0xAA} assert.NoError(t, packet.SetExtension(1, extension)) assert.True(t, packet.Extension) assert.Equal(t, uint16(0xBEDE), packet.ExtensionProfile) assert.Len(t, packet.Extensions, 1) assert.Equal(t, extension, packet.GetExtension(1)) } func TestRFC8285OneByteSetExtensionShouldSetCorrectExtensionProfileFor16ByteExtension(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } extension := []byte{ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, } err := packet.SetExtension(1, extension) assert.NoError(t, err, "Error setting extension") assert.Equal(t, uint16(0xBEDE), packet.ExtensionProfile) } func TestRFC8285OneByteSetExtensionShouldUpdateExistingExension(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } assert.Equal(t, []byte{0xAA}, packet.GetExtension(1)) extension := []byte{0xBB} err := packet.SetExtension(1, extension) assert.NoError(t, err, "Error setting extension") assert.Equal(t, extension, packet.GetExtension(1)) } func TestRFC8285OneByteSetExtensionShouldErrorWhenInvalidIDProvided(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } assert.Error( t, packet.SetExtension(0, []byte{0xBB}), "SetExtension did not error on invalid id", ) assert.Error( t, packet.SetExtension(15, []byte{0xBB}), "SetExtension did not error on invalid id", ) } func TestRFC8285OneByteExtensionTerminateProcessingWhenReservedIDEncountered(t *testing.T) { packet := &Packet{} reservedIDPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0xF0, 0xAA, 0x98, 0x36, 0xbe, 0x88, 0x9e, } assert.NoError( t, packet.Unmarshal(reservedIDPkt), "Unmarshal error on packet with reserved extension id", ) assert.Len(t, packet.Extensions, 0, "Extensions should be empty for invalid id") payload := reservedIDPkt[20:] assert.Equal(t, payload, packet.Payload) } func TestRFC8285OneByteExtensionTerminateProcessingWhenPaddingWithSizeEncountered(t *testing.T) { packet := &Packet{} reservedIDPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0x01, 0xAA, 0x98, 0x36, 0xbe, 0x88, 0x9e, } assert.NoError( t, packet.Unmarshal(reservedIDPkt), "Unmarshal error on packet with non-zero padding size", ) assert.Len(t, packet.Extensions, 0, "Extensions should be empty for non-zero padding size") payload := reservedIDPkt[20:] assert.Equal(t, payload, packet.Payload) } func TestRFC8285OneByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } assert.Error( t, packet.SetExtension(1, []byte{ 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, }), "SetExtension did not error on too large payload", ) } func TestRFC8285TwoByteSetExtensionShouldEnableExensionsWhenAdding(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } extension := []byte{ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, } assert.NoError(t, packet.SetExtension(1, extension)) assert.True(t, packet.Extension) assert.Equal(t, uint16(0x1000), packet.ExtensionProfile) assert.Len(t, packet.Extensions, 1) assert.Equal(t, extension, packet.GetExtension(1)) } func TestRFC8285TwoByteSetExtensionShouldUpdateExistingExension(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0x1000, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } assert.Equal(t, []byte{0xAA}, packet.GetExtension(1), "Extension value not initialize properly") extension := []byte{ 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, } err := packet.SetExtension(1, extension) assert.NoError(t, err) assert.Equal(t, packet.GetExtension(1), extension) } func TestRFC8285TwoByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } err := packet.SetExtension(1, []byte{ 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, }) assert.Error(t, err, "SetExtension did not error on too large payload") } func TestRFC8285Padding(t *testing.T) { header := &Header{} for n, payload := range [][]byte{ { 0b00010000, // header.Extension = true 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // SequenceNumber, Timestamp, SSRC 0xBE, 0xDE, // header.ExtensionProfile = extensionProfileOneByte 0, 1, // extensionLength 0, 0, 0, // padding 0x10, // extid and length }, { 0b00010000, // header.Extension = true 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // SequenceNumber, Timestamp, SSRC 0x10, 0x00, // header.ExtensionProfile = extensionProfileTwoByte 0, 1, // extensionLength 0, 0, // padding 0x01, 0x01, // extid and length }, } { _, err := header.Unmarshal(payload) assert.ErrorIs(t, err, errHeaderSizeInsufficientForExtension, "case %d", n) } } func TestRFC3550SetExtensionShouldErrorWhenNonZero(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0x1111, Extensions: []Extension{ {0, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } expect := []byte{0xBB} assert.NoError(t, packet.SetExtension(0, expect), "SetExtension should not error on valid id") actual := packet.GetExtension(0) assert.Equal(t, expect, actual) } func TestRFC3550SetExtensionShouldRaiseErrorWhenSettingNonzeroID(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0x1111, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: payload, } assert.Error(t, packet.SetExtension(1, []byte{0xBB}), "SetExtension should error on invalid id") } func TestUnmarshal_ErrorHandling(t *testing.T) { cases := map[string]struct { input []byte err error }{ "ShortHeader": { input: []byte{ 0x80, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, // timestamp 0x1c, 0x64, 0x27, // SSRC (one byte missing) }, err: errHeaderSizeInsufficient, }, "MissingCSRC": { input: []byte{ 0x81, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, // timestamp 0x1c, 0x64, 0x27, 0x82, // SSRC }, err: errHeaderSizeInsufficient, }, "MissingExtension": { input: []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, // timestamp 0x1c, 0x64, 0x27, 0x82, // SSRC }, err: errHeaderSizeInsufficientForExtension, }, "MissingExtensionData": { input: []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, // timestamp 0x1c, 0x64, 0x27, 0x82, // SSRC 0xBE, 0xDE, 0x00, 0x03, // specified to have 3 extensions, but actually not }, err: errHeaderSizeInsufficientForExtension, }, "MissingExtensionDataPayload": { input: []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, // timestamp 0x1c, 0x64, 0x27, 0x82, // SSRC 0xBE, 0xDE, 0x00, 0x01, // have 1 extension 0x12, 0x00, // length of the payload is expected to be 3, but actually have only 1 }, err: errHeaderSizeInsufficientForExtension, }, } for name, testCase := range cases { testCase := testCase t.Run(name, func(t *testing.T) { h := &Header{} _, err := h.Unmarshal(testCase.input) assert.ErrorIs(t, err, testCase.err) }) } } // https://github.com/pion/rtp/issues/275 func TestUnmarshal_OneByteExtensionWithoutRTPPayload(t *testing.T) { rawPkt := []byte{ 0x10, 0x64, 0x57, 0x49, 0x00, 0x00, 0x01, 0x90, 0x12, 0x34, 0xAB, 0xCD, 0xBE, 0xDE, 0x00, 0x01, // One-Byte extension header, 4 bytes 0x02, // ID=0, Len=2 (3 bytes data) 0x01, 0x02, 0x03, // Extension data } p := &Packet{} assert.NoError(t, p.Unmarshal(rawPkt)) } func TestUnmarshal_TwoByteExtensionWithoutRTPPayload(t *testing.T) { rawPkt := []byte{ 0x10, 0x64, 0x57, 0x49, 0x00, 0x00, 0x01, 0x90, 0x12, 0x34, 0xAB, 0xCD, 0x10, 0x00, 0x00, 0x01, // Two-Byte extension header, 4 bytes 0x02, 0x02, // ID=0, Len=2 (2 bytes data) 0x02, 0x03, // Extension data } p := &Packet{} assert.NoError(t, p.Unmarshal(rawPkt)) } func TestUnmarshal_NonStandardExtensionWithoutRTPPayload(t *testing.T) { rawPkt := []byte{ 0x10, 0x64, 0x57, 0x49, 0x00, 0x00, 0x01, 0x90, 0x12, 0x34, 0xAB, 0xCD, 0xAA, 0xAA, 0x00, 0x01, // Non-standard header extension 0xAAAA, 4 bytes 0x01, 0x02, 0x03, 0x04, // Extension data } p := &Packet{} assert.NoError(t, p.Unmarshal(rawPkt)) } func TestUnmarshal_EmptyOneByteExtensionWithoutRTPPayload(t *testing.T) { rawPkt := []byte{ 0x10, 0x64, 0x57, 0x49, 0x00, 0x00, 0x01, 0x90, 0x12, 0x34, 0xAB, 0xCD, 0xBE, 0xDE, 0x00, 0x00, // One-Byte extension header, 0 bytes } p := &Packet{} assert.NoError(t, p.Unmarshal(rawPkt)) } func TestUnmarshal_EmptyTwoByteExtensionWithoutRTPPayload(t *testing.T) { rawPkt := []byte{ 0x10, 0x64, 0x57, 0x49, 0x00, 0x00, 0x01, 0x90, 0x12, 0x34, 0xAB, 0xCD, 0x10, 0x00, 0x00, 0x00, // Two-Byte extension header, 0 bytes } p := &Packet{} assert.NoError(t, p.Unmarshal(rawPkt)) } func TestUnmarshal_EmptyNonStandardExtensionWithoutRTPPayload(t *testing.T) { rawPkt := []byte{ 0x10, 0x64, 0x57, 0x49, 0x00, 0x00, 0x01, 0x90, 0x12, 0x34, 0xAB, 0xCD, 0xAA, 0xAA, 0x00, 0x00, // Non-standard header extension 0xAAAA, 0 bytes } p := &Packet{} assert.NoError(t, p.Unmarshal(rawPkt)) } func TestRoundtrip(t *testing.T) { rawPkt := []byte{ 0x00, 0x10, 0x23, 0x45, 0x12, 0x34, 0x45, 0x67, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, } payload := rawPkt[12:] packet := &Packet{} assert.NoError(t, packet.Unmarshal(rawPkt)) assert.Equal(t, payload, packet.Payload) buf, err := packet.Marshal() assert.NoError(t, err) assert.Equal(t, rawPkt, buf) assert.Equal(t, payload, packet.Payload) } func TestCloneHeader(t *testing.T) { header := Header{ Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, } clone := header.Clone() assert.Equal(t, header, clone) header.CSRC = append(header.CSRC, 1) assert.NotEqual(t, len(clone.CSRC), len(header.CSRC), "Expected CSRC to be unchanged") header.Extensions[0].payload[0] = 0x1F assert.NotEqual(t, clone.Extensions[0].payload[0], byte(0x1F), "Expected extension to be unchanged") } func TestClonePacket(t *testing.T) { rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0x50, 0xAA, 0x00, 0x00, 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{ Payload: rawPkt[20:], } clone := packet.Clone() assert.Equal(t, packet, clone) packet.Payload[0] = 0x1F assert.NotEqual(t, clone.Payload[0], 0x1F, "Expected payload to be unchanged") } func TestMarshalRTPPacketFuncs(t *testing.T) { // packet with only padding rawPkt := []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x05, } parsedPacket := &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, PaddingSize: 5, }, Payload: []byte{}, } buf := make([]byte, 100) n, err := MarshalPacketTo(buf, &parsedPacket.Header, parsedPacket.Payload) assert.NoError(t, err) assert.Equal(t, len(rawPkt), n) assert.Equal(t, rawPkt, buf[:n]) assert.Equal(t, n, PacketMarshalSize(&parsedPacket.Header, parsedPacket.Payload)) hdrLen, packetLen := HeaderAndPacketMarshalSize(&parsedPacket.Header, parsedPacket.Payload) assert.Equal(t, parsedPacket.Header.MarshalSize(), hdrLen) assert.Equal(t, n, packetLen) } func TestDeprecatedPaddingSizeField(t *testing.T) { // packet with only padding rawPkt := []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x05, } parsedPacket := &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, }, Payload: []byte{}, PaddingSize: 5, } buf, err := parsedPacket.Marshal() assert.NoError(t, err) assert.Equal(t, rawPkt, buf) assert.EqualValues(t, 0, parsedPacket.Header.PaddingSize) assert.Equal(t, len(rawPkt), parsedPacket.MarshalSize()) assert.EqualValues(t, 0, parsedPacket.Header.PaddingSize) parsedPacket2 := parsedPacket.Clone() assert.EqualValues(t, 5, parsedPacket2.PaddingSize) assert.EqualValues(t, 0, parsedPacket2.Header.PaddingSize) } func TestSetExtensionWithProfile(t *testing.T) { t.Run("add two-byte extension due to the size > 16", func(t *testing.T) { h := Header{} assert.NoError(t, h.SetExtension(1, make([]byte, 2))) assert.NoError(t, h.SetExtension(2, make([]byte, 3))) // Adding another extension that requires two-byte header extension assert.NoError(t, h.SetExtensionWithProfile(3, make([]byte, 20), ExtensionProfileTwoByte)) assert.Equal(t, h.ExtensionProfile, uint16(ExtensionProfileTwoByte)) }) t.Run("add two-byte extension due to id > 14", func(t *testing.T) { h := Header{} assert.NoError(t, h.SetExtension(1, make([]byte, 2))) assert.NoError(t, h.SetExtension(2, make([]byte, 3))) // Adding another extension that requires two-byte header extension // because the extmap ID is greater than 14. assert.NoError(t, h.SetExtensionWithProfile(16, make([]byte, 4), ExtensionProfileTwoByte)) assert.Equal(t, h.ExtensionProfile, uint16(ExtensionProfileTwoByte)) }) t.Run("Downgrade 2 byte header Extension", func(t *testing.T) { pkt := []byte{ 0x90, 0x60, 0x00, 0x01, // V=2, P=0, X=1, CC=0; M=0, PT=96; sequence=1 0x00, 0x00, 0x00, 0x01, // timestamp=1 0x12, 0x34, 0x56, 0x78, // SSRC=0x12345678 0x10, 0x00, 0x00, 0x01, // profile=0x1000 (two-byte), length=1 (4 bytes) 0x01, 0x02, 0x00, 0x01, // id=1, len=2, data=0x00,0x01 (padded to 32-bit) } h := Header{} _, err := h.Unmarshal(pkt) assert.NoError(t, err) assert.Equal(t, h.ExtensionProfile, uint16(ExtensionProfileTwoByte)) assert.NoError(t, h.SetExtensionWithProfile(1, []byte{0x02, 0x03}, ExtensionProfileOneByte)) assert.Equal(t, h.ExtensionProfile, uint16(ExtensionProfileOneByte)) pkt, err = h.Marshal() assert.NoError(t, err) assert.Equal(t, pkt, []byte{ 0x90, 0x60, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x12, 0x34, 0x56, 0x78, 0xbe, 0xde, 0x00, 0x01, 0x11, 0x02, 0x03, 0x00, }) }) t.Run("Do not mutate packet for invalid extension", func(t *testing.T) { h := Header{} assert.NoError(t, h.SetExtension(1, make([]byte, 2))) assert.Error(t, h.SetExtensionWithProfile(16, make([]byte, 4096), ExtensionProfileTwoByte)) assert.Equal(t, h.ExtensionProfile, uint16(ExtensionProfileOneByte)) assert.Len(t, h.Extensions, 1) }) } func BenchmarkMarshal(b *testing.B) { rawPkt := []byte{ 0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{} err := packet.Unmarshal(rawPkt) if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err = packet.Marshal() if err != nil { b.Fatal(err) } } } func BenchmarkMarshalTo(b *testing.B) { rawPkt := []byte{ 0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x9e, } packet := &Packet{} err := packet.Unmarshal(rawPkt) if err != nil { b.Fatal(err) } buf := [100]byte{} b.ResetTimer() for i := 0; i < b.N; i++ { _, err = packet.MarshalTo(buf[:]) if err != nil { b.Fatal(err) } } } func BenchmarkUnmarshal(b *testing.B) { pkt := Packet{ Header: Header{ Extension: true, CSRC: []uint32{1, 2}, ExtensionProfile: ExtensionProfileTwoByte, Extensions: []Extension{ {id: 1, payload: []byte{3, 4}}, {id: 2, payload: []byte{5, 6}}, }, }, Payload: []byte{ 0x07, 0x08, 0x09, 0x0a, }, } rawPkt, errMarshal := pkt.Marshal() if errMarshal != nil { b.Fatal(errMarshal) } b.Run("SharedStruct", func(b *testing.B) { packet := &Packet{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { if err := packet.Unmarshal(rawPkt); err != nil { b.Fatal(err) } } }) b.Run("NewStruct", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { packet := &Packet{} if err := packet.Unmarshal(rawPkt); err != nil { b.Fatal(err) } } }) } // https://github.com/pion/rtp/issues/315 func TestMarshalSizePanic(t *testing.T) { hdr := &Header{ Extension: true, } assert.Equal(t, 16, hdr.MarshalSize()) } // https://github.com/pion/rtp/issues/315 func TestMarshalToPanic(t *testing.T) { hdr := &Header{ Extension: true, } buf := make([]byte, 16) n, err := hdr.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, 16, n) } func BenchmarkUnmarshalHeader(b *testing.B) { rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0x50, 0xAA, 0x00, 0x00, 0x98, 0x36, 0xbe, 0x88, } b.Run("NewStructWithoutCSRC", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { h := &Header{} if _, err := h.Unmarshal(rawPkt); err != nil { b.Fatal(err) } } }) rawPkt = []byte{ 0x92, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x00, 0x11, 0x11, 0x00, 0x00, 0x22, 0x22, 0xBE, 0xDE, 0x00, 0x01, 0x50, 0xAA, 0x00, 0x00, 0x98, 0x36, 0xbe, 0x88, } b.Run("NewStructWithCSRC", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { h := &Header{} if _, err := h.Unmarshal(rawPkt); err != nil { b.Fatal(err) } } }) } rtp-1.8.27/packetizer.go000066400000000000000000000113001512154406600151050ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "time" ) // Payloader payloads a byte array for use as rtp.Packet payloads. type Payloader interface { Payload(mtu uint16, payload []byte) [][]byte } // Packetizer packetizes a payload. type Packetizer interface { Packetize(payload []byte, samples uint32) []*Packet GeneratePadding(samples uint32) []*Packet EnableAbsSendTime(value int) SkipSamples(skippedSamples uint32) } type packetizer struct { MTU uint16 PayloadType uint8 SSRC uint32 Payloader Payloader Sequencer Sequencer Timestamp uint32 // Deprecated: will be removed in a future version. ClockRate uint32 // put extension numbers in here. If they're 0, the extension is disabled (0 is not a legal extension number) extensionNumbers struct { AbsSendTime int // http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time } timegen func() time.Time } // NewPacketizer returns a new instance of a Packetizer for a specific payloader. func NewPacketizer( mtu uint16, pt uint8, ssrc uint32, payloader Payloader, sequencer Sequencer, clockRate uint32, ) Packetizer { return &packetizer{ MTU: mtu, PayloadType: pt, SSRC: ssrc, Payloader: payloader, Sequencer: sequencer, Timestamp: globalMathRandomGenerator.Uint32(), ClockRate: clockRate, timegen: time.Now, } } // WithSSRC sets the SSRC for the Packetizer. func WithSSRC(ssrc uint32) func(*packetizer) { return func(p *packetizer) { p.SSRC = ssrc } } // WithPayloadType sets the PayloadType for the Packetizer. func WithPayloadType(pt uint8) func(*packetizer) { return func(p *packetizer) { p.PayloadType = pt } } // WithTimestamp sets the initial Timestamp for the Packetizer. func WithTimestamp(timestamp uint32) func(*packetizer) { return func(p *packetizer) { p.Timestamp = timestamp } } // PacketizerOption is a function that configures a RTP Packetizer. type PacketizerOption func(*packetizer) // NewPacketizerWithOptions returns a new instance of a Packetizer with the given options. func NewPacketizerWithOptions( mtu uint16, payloader Payloader, sequencer Sequencer, clockRate uint32, options ...PacketizerOption, ) Packetizer { packetizerInstance := &packetizer{ MTU: mtu, Payloader: payloader, Sequencer: sequencer, Timestamp: globalMathRandomGenerator.Uint32(), ClockRate: clockRate, timegen: time.Now, } for _, option := range options { option(packetizerInstance) } return packetizerInstance } func (p *packetizer) EnableAbsSendTime(value int) { p.extensionNumbers.AbsSendTime = value } // Packetize packetizes the payload of an RTP packet and returns one or more RTP packets. func (p *packetizer) Packetize(payload []byte, samples uint32) []*Packet { // Guard against an empty payload if len(payload) == 0 { p.SkipSamples(samples) return nil } payloads := p.Payloader.Payload(p.MTU-12, payload) packets := make([]*Packet, len(payloads)) for i, pp := range payloads { packets[i] = &Packet{ Header: Header{ Version: 2, Padding: false, Extension: false, Marker: i == len(payloads)-1, PayloadType: p.PayloadType, SequenceNumber: p.Sequencer.NextSequenceNumber(), Timestamp: p.Timestamp, // Figure out how to do timestamps SSRC: p.SSRC, }, Payload: pp, } } p.Timestamp += samples if len(packets) != 0 && p.extensionNumbers.AbsSendTime != 0 { sendTime := NewAbsSendTimeExtension(p.timegen()) // apply http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time b, err := sendTime.Marshal() if err != nil { return nil // never happens } err = packets[len(packets)-1].SetExtension(uint8(p.extensionNumbers.AbsSendTime), b) // nolint: gosec // G115 if err != nil { return nil // never happens } } return packets } // GeneratePadding returns required padding-only packages. func (p *packetizer) GeneratePadding(samples uint32) []*Packet { // Guard against an empty payload if samples == 0 { return nil } packets := make([]*Packet, samples) for i := 0; i < int(samples); i++ { packets[i] = &Packet{ Header: Header{ Version: 2, Padding: true, Extension: false, Marker: false, PayloadType: p.PayloadType, SequenceNumber: p.Sequencer.NextSequenceNumber(), Timestamp: p.Timestamp, // Use latest timestamp SSRC: p.SSRC, PaddingSize: 255, }, } } return packets } // SkipSamples causes a gap in sample count between Packetize requests so the // RTP payloads produced have a gap in timestamps. func (p *packetizer) SkipSamples(skippedSamples uint32) { p.Timestamp += skippedSamples } rtp-1.8.27/packetizer_test.go000066400000000000000000000251571512154406600161630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "fmt" "testing" "time" "github.com/pion/rtp/codecs" "github.com/stretchr/testify/assert" ) func TestPacketizer(t *testing.T) { multiplepayload := make([]byte, 128) // use the G722 payloader here, because it's very simple and all 0s is valid G722 data. packetizer := NewPacketizer(100, 98, 0x1234ABCD, &codecs.G722Payloader{}, NewRandomSequencer(), 90000) packets := packetizer.Packetize(multiplepayload, 2000) expectedLen := 2 if len(packets) != expectedLen { packetlengths := "" for i := 0; i < len(packets); i++ { packetlengths += fmt.Sprintf("Packet %d length %d\n", i, len(packets[i].Payload)) } assert.Failf( t, "Packetize failed", "Generated %d packets instead of %d\n%s", len(packets), expectedLen, packetlengths, ) } } func TestPacketizer_AbsSendTime(t *testing.T) { // use the G722 payloader here, because it's very simple and all 0s is valid G722 data. pktizer := NewPacketizer(100, 98, 0x1234ABCD, &codecs.G722Payloader{}, NewFixedSequencer(1234), 90000) p, ok := pktizer.(*packetizer) assert.True(t, ok, "Failed to cast to *packetizer") p.Timestamp = 45678 p.timegen = func() time.Time { return time.Date(1985, time.June, 23, 4, 0, 0, 0, time.FixedZone("UTC-5", -5*60*60)) // (0xa0c65b1000000000>>14) & 0xFFFFFF = 0x400000 } pktizer.EnableAbsSendTime(1) payload := []byte{0x11, 0x12, 0x13, 0x14} packets := pktizer.Packetize(payload, 2000) expected := &Packet{ Header: Header{ Version: 2, Padding: false, Extension: true, Marker: true, PayloadType: 98, SequenceNumber: 1234, Timestamp: 45678, SSRC: 0x1234ABCD, ExtensionProfile: 0xBEDE, Extensions: []Extension{ { id: 1, payload: []byte{0x40, 0, 0}, }, }, }, Payload: []byte{0x11, 0x12, 0x13, 0x14}, } assert.Lenf(t, packets, 1, "Generated %d packets instead of 1", len(packets)) assert.Equal(t, expected, packets[0], "Packetize failed") } func TestPacketizer_Roundtrip(t *testing.T) { multiplepayload := make([]byte, 128) packetizer := NewPacketizer(100, 98, 0x1234ABCD, &codecs.G722Payloader{}, NewRandomSequencer(), 90000) packets := packetizer.Packetize(multiplepayload, 1000) rawPkts := make([][]byte, 0, 1400) for _, pkt := range packets { raw, err := pkt.Marshal() assert.NoError(t, err) rawPkts = append(rawPkts, raw) } for ndx, raw := range rawPkts { expectedPkt := packets[ndx] pkt := &Packet{} assert.NoError(t, pkt.Unmarshal(raw)) assert.Equal(t, len(raw), pkt.MarshalSize()) assert.Equal(t, expectedPkt.MarshalSize(), pkt.MarshalSize()) assert.Equal(t, expectedPkt.Version, pkt.Version) assert.Equal(t, expectedPkt.Padding, pkt.Padding) assert.Equal(t, expectedPkt.Extension, pkt.Extension) assert.Equal(t, expectedPkt.Marker, pkt.Marker) assert.Equal(t, expectedPkt.PayloadType, pkt.PayloadType) assert.Equal(t, expectedPkt.SequenceNumber, pkt.SequenceNumber) assert.Equal(t, expectedPkt.Timestamp, pkt.Timestamp) assert.Equal(t, expectedPkt.SSRC, pkt.SSRC) assert.Equal(t, expectedPkt.CSRC, pkt.CSRC) assert.Equal(t, expectedPkt.ExtensionProfile, pkt.ExtensionProfile) assert.Equal(t, expectedPkt.Extensions, pkt.Extensions) assert.Equal(t, expectedPkt.Payload, pkt.Payload) pkt.PaddingSize = 0 pkt.Header.PaddingSize = 0 assert.Equal(t, expectedPkt, pkt) } } func TestPacketizer_GeneratePadding(t *testing.T) { pktizer := NewPacketizer(100, 98, 0x1234ABCD, &codecs.G722Payloader{}, NewFixedSequencer(1234), 90000) packets := pktizer.GeneratePadding(5) assert.Len(t, packets, 5, "Should generate exactly 5 padding packets") for i, pkt := range packets { assert.Equal(t, true, pkt.Header.Padding, "Packet %d should have Padding set to true", i) assert.Equal(t, byte(255), pkt.Header.PaddingSize, "Packet %d should have PaddingSize set to 255", i) assert.Equal(t, byte(0), pkt.PaddingSize, "Packet %d should have PaddingSize set to 0", i) assert.Nil(t, pkt.Payload, "Packet %d should have no Payload", i) } } func TestNewPacketizerWithOptions_DefaultValues(t *testing.T) { pack := NewPacketizerWithOptions(100, &codecs.G722Payloader{}, NewRandomSequencer(), 90000) p, ok := pack.(*packetizer) assert.True(t, ok, "Failed to cast to *packetizer") assert.Equal(t, uint16(100), p.MTU) assert.Equal(t, uint8(0), p.PayloadType) assert.Equal(t, uint32(0), p.SSRC) assert.NotZero(t, p.Timestamp) assert.Equal(t, uint32(90000), p.ClockRate) } func TestNewPacketizerWithOptions_WithOptions(t *testing.T) { pack := NewPacketizerWithOptions( 100, &codecs.G722Payloader{}, NewRandomSequencer(), 90000, WithSSRC(0x1234ABCD), WithPayloadType(98), WithTimestamp(45678), ) p, ok := pack.(*packetizer) assert.True(t, ok, "Failed to cast to *packetizer") assert.Equal(t, uint16(100), p.MTU) assert.Equal(t, uint8(98), p.PayloadType) assert.Equal(t, uint32(0x1234ABCD), p.SSRC) assert.Equal(t, uint32(45678), p.Timestamp) assert.Equal(t, uint32(90000), p.ClockRate) payload := []byte{0x11, 0x12, 0x13, 0x14} packets := pack.Packetize(payload, 2000) assert.Len(t, packets, 1, "Should generate exactly one packet") assert.Equal(t, uint8(98), packets[0].PayloadType) assert.Equal(t, uint32(0x1234ABCD), packets[0].SSRC) assert.Equal(t, uint32(45678), packets[0].Timestamp) } func TestNewPacketizerWithOptions_PartialOptions(t *testing.T) { pack := NewPacketizerWithOptions( 100, &codecs.G722Payloader{}, NewRandomSequencer(), 90000, WithPayloadType(98), ) p, ok := pack.(*packetizer) assert.True(t, ok, "Failed to cast to *packetizer") assert.Equal(t, uint16(100), p.MTU) assert.Equal(t, uint8(98), p.PayloadType) assert.Equal(t, uint32(0), p.SSRC) assert.NotZero(t, p.Timestamp) assert.Equal(t, uint32(90000), p.ClockRate) } func TestPacketizer_Empty_Payload(t *testing.T) { pktizer := NewPacketizer(100, 98, 0x1234ABCD, &codecs.G722Payloader{}, NewFixedSequencer(1234), 90000) const expectedSamples = uint32(4000) prevTimestamp := uint32(0) for i := 0; i < 10; i++ { payload := []byte{0x11, 0x12, 0x13, 0x14} isEmptyPayload := i%2 == 0 if isEmptyPayload { payload = nil } packets := pktizer.Packetize(payload, 2000) if isEmptyPayload { assert.Len(t, packets, 0) } else { assert.Len(t, packets, 1) if prevTimestamp != 0 { assert.Equal(t, packets[0].Timestamp-prevTimestamp, expectedSamples) } prevTimestamp = packets[0].Timestamp } } } func FuzzPacketizer_Packetize_G722(f *testing.F) { // mixed seeds. f.Add(uint16(100), uint8(98), uint32(0x1234ABCD), uint32(960), false, []byte{0}) f.Add(uint16(100), uint8(98), uint32(0x00000001), uint32(0), false, []byte{}) f.Add(uint16(12), uint8(0), uint32(0), uint32(1), true, []byte{1, 2, 3, 4}) f.Add(uint16(1500), uint8(120), uint32(0xCAFEBABE), uint32(480), true, make([]byte, 4096)) f.Add(uint16(1), uint8(34), uint32(7), uint32(160), false, make([]byte, 32)) f.Fuzz(func(t *testing.T, mtu uint16, pt uint8, ssrc uint32, samples uint32, enableAST bool, payload []byte) { if len(payload) > 1<<16 { payload = payload[:1<<16] } packetizer := NewPacketizerWithOptions( mtu, &codecs.G722Payloader{}, NewFixedSequencer(0), 90000, WithPayloadType(pt), WithSSRC(ssrc), WithTimestamp(0xAABBCCDD), ) if enableAST { packetizer.EnableAbsSendTime(1) } packets := packetizer.Packetize(payload, samples) if len(payload) == 0 { assert.Nil(t, packets) return } eff := int(mtu - 12) if eff == 0 { assert.Equal(t, 0, len(packets)) return } assert.GreaterOrEqual(t, len(packets), 1) for i, packet := range packets { assert.Equal(t, uint8(2), packet.Version) assert.Equal(t, pt, packet.PayloadType) assert.Equal(t, ssrc, packet.SSRC) if i == len(packets)-1 { assert.True(t, packet.Marker) if enableAST { assert.True(t, packet.Extension) raw, err := packet.Marshal() assert.NoError(t, err) var back Packet assert.NoError(t, back.Unmarshal(raw)) } } else { assert.False(t, packet.Marker) } if mtu >= 12 && !enableAST { raw, err := packet.Marshal() assert.NoError(t, err) assert.LessOrEqual(t, len(raw), int(mtu)) } else { raw, err := packet.Marshal() assert.NoError(t, err) var back Packet assert.NoError(t, back.Unmarshal(raw)) } } }) } func FuzzPacketizer_SkipSamples_And_Timestamps(f *testing.F) { // mixed seeds. f.Add(uint16(1200), uint32(0), uint32(480), uint32(960), uint16(100), uint16(200), false) f.Add(uint16(200), uint32(160), uint32(160), uint32(160), uint16(10), uint16(20), true) f.Add(uint16(20), uint32(32000), uint32(0), uint32(1), uint16(0), uint16(1), false) f.Fuzz(func( t *testing.T, mtu uint16, skip uint32, samples1 uint32, samples2 uint32, len1 uint16, len2 uint16, enableAST bool, ) { p1 := make([]byte, int(len1)) for i := range p1 { p1[i] = byte(i) } p2 := make([]byte, int(len2)) for i := range p2 { p2[i] = byte(255 - i) } const startTS = uint32(0x10203040) packetizer := NewPacketizerWithOptions( mtu, &codecs.G722Payloader{}, NewFixedSequencer(1000), 90000, WithPayloadType(111), WithSSRC(0xFEEDBEEF), WithTimestamp(startTS), ) if enableAST { packetizer.EnableAbsSendTime(1) } packetizer.SkipSamples(skip) pkts1 := packetizer.Packetize(p1, samples1) if len(p1) == 0 { assert.Nil(t, pkts1) } else { assert.GreaterOrEqual(t, len(pkts1), 1) assert.Equal(t, startTS+skip, pkts1[0].Timestamp) } pkts2 := packetizer.Packetize(p2, samples2) if len(p2) == 0 { assert.Nil(t, pkts2) } else { assert.GreaterOrEqual(t, len(pkts2), 1) expectedTS2 := startTS + skip + samples1 assert.Equal(t, expectedTS2, pkts2[0].Timestamp) } for _, p := range append(pkts1, pkts2...) { raw, err := p.Marshal() assert.NoError(t, err) var back Packet assert.NoError(t, back.Unmarshal(raw)) } }) } func FuzzPacketizer_GeneratePadding(f *testing.F) { // mixed seeds. f.Add(uint32(0)) f.Add(uint32(1)) f.Add(uint32(5)) f.Add(uint32(16)) f.Fuzz(func(t *testing.T, samples uint32) { samples %= 64 packetizer := NewPacketizerWithOptions( 1200, &codecs.G722Payloader{}, NewFixedSequencer(0), 90000, ) pads := packetizer.GeneratePadding(samples) if samples == 0 { assert.Nil(t, pads) return } assert.Len(t, pads, int(samples)) for _, p := range pads { assert.True(t, p.Header.Padding) assert.Equal(t, byte(255), p.Header.PaddingSize) assert.Nil(t, p.Payload) raw, err := p.Marshal() assert.NoError(t, err) var back Packet assert.NoError(t, back.Unmarshal(raw)) assert.True(t, back.Padding) } }) } rtp-1.8.27/partitionheadchecker.go000066400000000000000000000004251512154406600171320ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp // PartitionHeadChecker is the interface that checks whether the packet is keyframe or not. type PartitionHeadChecker interface { IsPartitionHead([]byte) bool } rtp-1.8.27/payload_types.go000066400000000000000000000062341512154406600156330ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2024 The Pion community // SPDX-License-Identifier: MIT package rtp // https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml // https://en.wikipedia.org/wiki/RTP_payload_formats // Audio Payload Types as defined in https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml const ( // PayloadTypePCMU is a payload type for ITU-T G.711 PCM μ-Law audio 64 kbit/s (RFC 3551). PayloadTypePCMU = 0 // PayloadTypeGSM is a payload type for European GSM Full Rate audio 13 kbit/s (GSM 06.10). PayloadTypeGSM = 3 // PayloadTypeG723 is a payload type for ITU-T G.723.1 audio (RFC 3551). PayloadTypeG723 = 4 // PayloadTypeDVI4_8000 is a payload type for IMA ADPCM audio 32 kbit/s (RFC 3551). PayloadTypeDVI4_8000 = 5 // PayloadTypeDVI4_16000 is a payload type for IMA ADPCM audio 64 kbit/s (RFC 3551). PayloadTypeDVI4_16000 = 6 // PayloadTypeLPC is a payload type for Experimental Linear Predictive Coding audio 5.6 kbit/s (RFC 3551). PayloadTypeLPC = 7 // PayloadTypePCMA is a payload type for ITU-T G.711 PCM A-Law audio 64 kbit/s (RFC 3551). PayloadTypePCMA = 8 // PayloadTypeG722 is a payload type for ITU-T G.722 audio 64 kbit/s (RFC 3551). PayloadTypeG722 = 9 // PayloadTypeL16Stereo is a payload type for Linear PCM 16-bit Stereo audio 1411.2 kbit/s, uncompressed (RFC 3551). PayloadTypeL16Stereo = 10 // PayloadTypeL16Mono is a payload type for Linear PCM 16-bit audio 705.6 kbit/s, uncompressed (RFC 3551). PayloadTypeL16Mono = 11 // PayloadTypeQCELP is a payload type for Qualcomm Code Excited Linear Prediction (RFC 2658, RFC 3551). PayloadTypeQCELP = 12 // PayloadTypeCN is a payload type for Comfort noise (RFC 3389). PayloadTypeCN = 13 // PayloadTypeMPA is a payload type for MPEG-1 or MPEG-2 audio only (RFC 3551, RFC 2250). PayloadTypeMPA = 14 // PayloadTypeG728 is a payload type for ITU-T G.728 audio 16 kbit/s (RFC 3551). PayloadTypeG728 = 15 // PayloadTypeDVI4_11025 is a payload type for IMA ADPCM audio 44.1 kbit/s (RFC 3551). PayloadTypeDVI4_11025 = 16 // PayloadTypeDVI4_22050 is a payload type for IMA ADPCM audio 88.2 kbit/s (RFC 3551). PayloadTypeDVI4_22050 = 17 // PayloadTypeG729 is a payload type for ITU-T G.729 and G.729a audio 8 kbit/s (RFC 3551, RFC 3555). PayloadTypeG729 = 18 ) // Video Payload Types as defined in https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml const ( // PayloadTypeCELLB is a payload type for Sun CellB video (RFC 2029). PayloadTypeCELLB = 25 // PayloadTypeJPEG is a payload type for JPEG video (RFC 2435). PayloadTypeJPEG = 26 // PayloadTypeNV is a payload type for Xerox PARC's Network Video (nv, RFC 3551). PayloadTypeNV = 28 // PayloadTypeH261 is a payload type for ITU-T H.261 video (RFC 4587). PayloadTypeH261 = 31 // PayloadTypeMPV is a payload type for MPEG-1 and MPEG-2 video (RFC 2250). PayloadTypeMPV = 32 // PayloadTypeMP2T is a payload type for MPEG-2 transport stream (RFC 2250). PayloadTypeMP2T = 33 // PayloadTypeH263 is a payload type for H.263 video, first version (1996, RFC 3551, RFC 2190). PayloadTypeH263 = 34 ) const ( // PayloadTypeFirstDynamic is a first non-static payload type. PayloadTypeFirstDynamic = 35 ) rtp-1.8.27/pkg/000077500000000000000000000000001512154406600132035ustar00rootroot00000000000000rtp-1.8.27/pkg/frame/000077500000000000000000000000001512154406600142755ustar00rootroot00000000000000rtp-1.8.27/pkg/frame/av1.go000066400000000000000000000012041512154406600153100ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT // Package frame is deprecated. package frame import ( "github.com/pion/rtp/codecs/av1/frame" ) // AV1 represents a collection of OBUs given a stream of AV1 Packets. // Each AV1 RTP Packet is a collection of OBU Elements. Each OBU Element may be a full OBU, or just a fragment of one. // AV1 provides the tools to construct a collection of OBUs from a collection of OBU Elements. This structure // contains an internal cache and should be used for the entire RTP Stream. // // Deprecated: moved into codecs/av1/frame. type AV1 = frame.AV1 rtp-1.8.27/pkg/obu/000077500000000000000000000000001512154406600137705ustar00rootroot00000000000000rtp-1.8.27/pkg/obu/leb128.go000066400000000000000000000014651512154406600153220ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT // Package obu is deprecated. package obu import ( "github.com/pion/rtp/codecs/av1/obu" ) // ErrFailedToReadLEB128 indicates that a buffer ended before a LEB128 value could be successfully read // // Deprecated: moved into codecs/av1/obu. var ErrFailedToReadLEB128 = obu.ErrFailedToReadLEB128 // EncodeLEB128 encodes a uint as LEB128 // // Deprecated: moved into codecs/av1/obu. func EncodeLEB128(in uint) (out uint) { return obu.EncodeLEB128(in) } // ReadLeb128 scans an buffer and decodes a Leb128 value. // If the end of the buffer is reached and all MSB are set // an error is returned // // Deprecated: moved into codecs/av1/obu. func ReadLeb128(in []byte) (uint, uint, error) { return obu.ReadLeb128(in) } rtp-1.8.27/playoutdelayextension.go000066400000000000000000000042761512154406600174330ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "encoding/binary" "errors" "io" ) const ( playoutDelayExtensionSize = 3 playoutDelayMaxValue = (1 << 12) - 1 ) var errPlayoutDelayInvalidValue = errors.New("invalid playout delay value") // PlayoutDelayExtension is a extension payload format in // http://www.webrtc.org/experiments/rtp-hdrext/playout-delay // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | len=2 | MIN delay | MAX delay | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // . type PlayoutDelayExtension struct { MinDelay, MaxDelay uint16 } // MarshalSize returns the size of the PlayoutDelayExtension once marshaled. func (p PlayoutDelayExtension) MarshalSize() int { return playoutDelayExtensionSize } // MarshalTo marshals the extension to the given buffer. // Returns io.ErrShortBuffer if buf is too small. func (p PlayoutDelayExtension) MarshalTo(buf []byte) (int, error) { if p.MinDelay > playoutDelayMaxValue || p.MaxDelay > playoutDelayMaxValue { return 0, errPlayoutDelayInvalidValue } if len(buf) < playoutDelayExtensionSize { return 0, io.ErrShortBuffer } buf[0] = byte(p.MinDelay >> 4) buf[1] = byte(p.MinDelay<<4) | byte(p.MaxDelay>>8) buf[2] = byte(p.MaxDelay) return playoutDelayExtensionSize, nil } // Marshal serializes the members to buffer. func (p PlayoutDelayExtension) Marshal() ([]byte, error) { if p.MinDelay > playoutDelayMaxValue || p.MaxDelay > playoutDelayMaxValue { return nil, errPlayoutDelayInvalidValue } return []byte{ byte(p.MinDelay >> 4), byte(p.MinDelay<<4) | byte(p.MaxDelay>>8), byte(p.MaxDelay), }, nil } // Unmarshal parses the passed byte slice and stores the result in the members. func (p *PlayoutDelayExtension) Unmarshal(rawData []byte) error { if len(rawData) < playoutDelayExtensionSize { return errTooSmall } p.MinDelay = binary.BigEndian.Uint16(rawData[0:2]) >> 4 p.MaxDelay = binary.BigEndian.Uint16(rawData[1:3]) & 0x0FFF return nil } rtp-1.8.27/playoutdelayextension_test.go000066400000000000000000000044121512154406600204620ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "io" "testing" "github.com/stretchr/testify/assert" ) func TestPlayoutDelayExtensionTooSmall(t *testing.T) { t1 := PlayoutDelayExtension{} var rawData []byte err := t1.Unmarshal(rawData) assert.ErrorIs(t, err, errTooSmall) } func TestPlayoutDelayExtensionTooLarge(t *testing.T) { t1 := PlayoutDelayExtension{MinDelay: 1 << 12, MaxDelay: 1 << 12} _, err := t1.Marshal() assert.ErrorIs(t, err, errPlayoutDelayInvalidValue) _, err = t1.MarshalTo(make([]byte, 10)) assert.ErrorIs(t, err, errPlayoutDelayInvalidValue) } func TestPlayoutDelayExtension(t *testing.T) { t1 := PlayoutDelayExtension{} rawData := []byte{ 0x01, 0x01, 0x00, } err := t1.Unmarshal(rawData) assert.NoError(t, err) t2 := PlayoutDelayExtension{ MinDelay: 1 << 4, MaxDelay: 1 << 8, } assert.Equal(t, t1, t2) dstData, _ := t2.Marshal() assert.Equal(t, dstData, rawData) } func TestPlayoutDelayExtensionExtraBytes(t *testing.T) { t1 := PlayoutDelayExtension{} rawData := []byte{ 0x01, 0x01, 0x00, 0xff, 0xff, } err := t1.Unmarshal(rawData) assert.NoError(t, err) t2 := PlayoutDelayExtension{ MinDelay: 1 << 4, MaxDelay: 1 << 8, } assert.Equal(t, t1, t2) } func TestPlayoutDelayExtensionMarshalTo(t *testing.T) { ext := PlayoutDelayExtension{MinDelay: 100, MaxDelay: 200} buf := make([]byte, ext.MarshalSize()) n, err := ext.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, ext.MarshalSize(), n) expected, _ := ext.Marshal() assert.Equal(t, expected, buf) _, err = ext.MarshalTo(nil) assert.ErrorIs(t, err, io.ErrShortBuffer) } //nolint:gochecknoglobals var ( playoutDelaySink []byte playoutDelayBuf = make([]byte, playoutDelayExtensionSize) playoutDelaySinkInt int ) func BenchmarkPlayoutDelayExtension_Marshal(b *testing.B) { ext := PlayoutDelayExtension{MinDelay: 100, MaxDelay: 200} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { playoutDelaySink, _ = ext.Marshal() } } func BenchmarkPlayoutDelayExtension_MarshalTo(b *testing.B) { ext := PlayoutDelayExtension{MinDelay: 100, MaxDelay: 200} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { playoutDelaySinkInt, _ = ext.MarshalTo(playoutDelayBuf) } } rtp-1.8.27/rand.go000066400000000000000000000005001512154406600136700ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "github.com/pion/randutil" ) // Use global random generator to properly seed by crypto grade random. var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals rtp-1.8.27/renovate.json000066400000000000000000000001731512154406600151410ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "github>pion/renovate-config" ] } rtp-1.8.27/rtp.go000066400000000000000000000002521512154406600135550ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT // Package rtp provides RTP packetizer and depacketizer package rtp rtp-1.8.27/sequencer.go000066400000000000000000000033531512154406600147470ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "sync/atomic" ) // Sequencer generates sequential sequence numbers for building RTP packets. type Sequencer interface { NextSequenceNumber() uint16 RollOverCount() uint64 } // maxInitialRandomSequenceNumber is the maximum value used for the initial sequence // number when using NewRandomSequencer(). // This uses only half the potential sequence number space to avoid issues decrypting // SRTP when the sequence number starts near the rollover and there is packet loss. // See https://webrtc-review.googlesource.com/c/src/+/358360 const maxInitialRandomSequenceNumber = 1<<15 - 1 // NewRandomSequencer returns a new sequencer starting from a random sequence // number. func NewRandomSequencer() Sequencer { s := &sequencer{} s.state.Store(uint64(globalMathRandomGenerator.Intn(maxInitialRandomSequenceNumber))) // nolint: gosec // G115 return s } // NewFixedSequencer returns a new sequencer starting from a specific // sequence number. func NewFixedSequencer(s uint16) Sequencer { seq := &sequencer{} seq.state.Store(uint64(s - 1)) // -1 because the first sequence number prepends 1 return seq } type sequencer struct { // state packs both sequenceNumber (lower 16 bits) and rollOverCount (upper 48 bits) // into a single atomic uint64 state atomic.Uint64 } // NextSequenceNumber increment and returns a new sequence number for // building RTP packets. func (s *sequencer) NextSequenceNumber() uint16 { return uint16(s.state.Add(1)) // nolint: gosec // G115 } // RollOverCount returns the amount of times the 16bit sequence number // has wrapped. func (s *sequencer) RollOverCount() uint64 { return s.state.Load() >> 16 } rtp-1.8.27/sequencer_test.go000066400000000000000000000046301512154406600160050ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "sync" "testing" "github.com/stretchr/testify/assert" ) func TestSequencerBasic(t *testing.T) { sequencer := NewFixedSequencer(1) assert.Equal(t, uint16(1), sequencer.NextSequenceNumber()) assert.Equal(t, uint64(0), sequencer.RollOverCount()) } func TestSequencerWrapAround(t *testing.T) { sequencer := NewFixedSequencer(65535) assert.Equal(t, uint16(65535), sequencer.NextSequenceNumber()) assert.Equal(t, uint16(0), sequencer.NextSequenceNumber()) assert.Equal(t, uint64(1), sequencer.RollOverCount()) assert.Equal(t, uint16(1), sequencer.NextSequenceNumber()) } func TestSequencerMultipleRollovers(t *testing.T) { sequencer := NewFixedSequencer(65535) sequencer.NextSequenceNumber() sequencer.NextSequenceNumber() assert.Equal(t, uint64(1), sequencer.RollOverCount()) for i := 0; i < 65536; i++ { sequencer.NextSequenceNumber() } assert.Equal(t, uint64(2), sequencer.RollOverCount()) } func TestRandomSequencer(t *testing.T) { sequencer1 := NewRandomSequencer() sequencer2 := NewRandomSequencer() seq1 := sequencer1.NextSequenceNumber() seq2 := sequencer2.NextSequenceNumber() assert.Less(t, seq1, uint16(maxInitialRandomSequenceNumber)) assert.Less(t, seq2, uint16(maxInitialRandomSequenceNumber)) } func TestSequencerConcurrent(t *testing.T) { sequencer := NewFixedSequencer(1) const numGoroutines = 100 const numIterations = 100 var wg sync.WaitGroup wg.Add(numGoroutines) results := make([][]uint16, numGoroutines) for i := 0; i < numGoroutines; i++ { go func(idx int) { defer wg.Done() results[idx] = make([]uint16, numIterations) for j := 0; j < numIterations; j++ { results[idx][j] = sequencer.NextSequenceNumber() } }(i) } wg.Wait() seen := make(map[uint16]int) for i := 0; i < numGoroutines; i++ { for j := 0; j < numIterations; j++ { seen[results[i][j]]++ } } for seq, count := range seen { assert.Equal(t, 1, count, "Sequence number %d appeared %d times", seq, count) if count != 1 { break } } assert.Equal(t, numGoroutines*numIterations, len(seen)) } func TestSequencerRollOverCountDuringWrap(t *testing.T) { sequencer := NewFixedSequencer(65535) sequencer.NextSequenceNumber() assert.Equal(t, uint64(0), sequencer.RollOverCount()) sequencer.NextSequenceNumber() assert.Equal(t, uint64(1), sequencer.RollOverCount()) } rtp-1.8.27/transportccextension.go000066400000000000000000000036241512154406600172550ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "encoding/binary" "io" ) const ( // transport-wide sequence. transportCCExtensionSize = 2 ) // TransportCCExtension is a extension payload format in // https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0xBE | 0xDE | length=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | L=1 |transport-wide sequence number | zero padding | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // . type TransportCCExtension struct { TransportSequence uint16 } // MarshalSize returns the size of the TransportCCExtension once marshaled. func (t TransportCCExtension) MarshalSize() int { return transportCCExtensionSize } // MarshalTo marshals the extension to the given buffer. // Returns io.ErrShortBuffer if buf is too small. func (t TransportCCExtension) MarshalTo(buf []byte) (int, error) { if len(buf) < transportCCExtensionSize { return 0, io.ErrShortBuffer } binary.BigEndian.PutUint16(buf[0:2], t.TransportSequence) return transportCCExtensionSize, nil } // Marshal serializes the members to buffer. func (t TransportCCExtension) Marshal() ([]byte, error) { buf := make([]byte, transportCCExtensionSize) binary.BigEndian.PutUint16(buf[0:2], t.TransportSequence) return buf, nil } // Unmarshal parses the passed byte slice and stores the result in the members. func (t *TransportCCExtension) Unmarshal(rawData []byte) error { if len(rawData) < transportCCExtensionSize { return errTooSmall } t.TransportSequence = binary.BigEndian.Uint16(rawData[0:2]) return nil } rtp-1.8.27/transportccextension_test.go000066400000000000000000000036331512154406600203140ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "io" "testing" "github.com/stretchr/testify/assert" ) func TestTransportCCExtensionTooSmall(t *testing.T) { t1 := TransportCCExtension{} rawData := []byte{} err := t1.Unmarshal(rawData) assert.ErrorIs(t, err, errTooSmall) } func TestTransportCCExtension(t *testing.T) { t1 := TransportCCExtension{} rawData := []byte{ 0x00, 0x02, } err := t1.Unmarshal(rawData) assert.NoError(t, err) t2 := TransportCCExtension{ TransportSequence: 2, } assert.Equal(t, t1, t2) dstData, _ := t2.Marshal() assert.Equal(t, dstData, rawData) } func TestTransportCCExtensionExtraBytes(t *testing.T) { t1 := TransportCCExtension{} rawData := []byte{ 0x00, 0x02, 0x00, 0xff, 0xff, } err := t1.Unmarshal(rawData) assert.NoError(t, err) t2 := TransportCCExtension{ TransportSequence: 2, } assert.Equal(t, t1, t2) } func TestTransportCCExtensionMarshalTo(t *testing.T) { ext := TransportCCExtension{TransportSequence: 1234} buf := make([]byte, ext.MarshalSize()) n, err := ext.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, ext.MarshalSize(), n) expected, _ := ext.Marshal() assert.Equal(t, expected, buf) _, err = ext.MarshalTo(nil) assert.ErrorIs(t, err, io.ErrShortBuffer) } //nolint:gochecknoglobals var ( transportCCSink []byte transportCCBuf = make([]byte, transportCCExtensionSize) transportCCSinkInt int ) func BenchmarkTransportCCExtension_Marshal(b *testing.B) { ext := TransportCCExtension{TransportSequence: 1234} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { transportCCSink, _ = ext.Marshal() } } func BenchmarkTransportCCExtension_MarshalTo(b *testing.B) { ext := TransportCCExtension{TransportSequence: 1234} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { transportCCSinkInt, _ = ext.MarshalTo(transportCCBuf) } } rtp-1.8.27/vlaextension.go000066400000000000000000000271211512154406600154730ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "encoding/binary" "errors" "fmt" "io" "strings" "github.com/pion/rtp/codecs/av1/obu" ) var ( // ErrVLATooShort is returned when payload is too short. ErrVLATooShort = errors.New("VLA payload too short") // ErrVLAInvalidStreamCount is returned when RTP stream count is invalid. ErrVLAInvalidStreamCount = errors.New("invalid RTP stream count in VLA") // ErrVLAInvalidStreamID is returned when RTP stream ID is invalid. ErrVLAInvalidStreamID = errors.New("invalid RTP stream ID in VLA") // ErrVLAInvalidSpatialID is returned when spatial ID is invalid. ErrVLAInvalidSpatialID = errors.New("invalid spatial ID in VLA") // ErrVLADuplicateSpatialID is returned when spatial ID is invalid. ErrVLADuplicateSpatialID = errors.New("duplicate spatial ID in VLA") // ErrVLAInvalidTemporalLayer is returned when temporal layer is invalid. ErrVLAInvalidTemporalLayer = errors.New("invalid temporal layer in VLA") ) // SpatialLayer is a spatial layer in VLA. type SpatialLayer struct { RTPStreamID int SpatialID int TargetBitrates []int // target bitrates per temporal layer // Following members are valid only when HasResolutionAndFramerate is true Width int Height int Framerate int } // VLA is a Video Layer Allocation (VLA) extension. // See https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-layers-allocation00 type VLA struct { RTPStreamID int // 0-origin RTP stream ID (RID) this allocation is sent on (0..3) RTPStreamCount int // Number of RTP streams (1..4) ActiveSpatialLayer []SpatialLayer HasResolutionAndFramerate bool } type vlaMarshalingContext struct { slMBs [4]uint8 slIndices [4][4]int // index into ActiveSpatialLayer, -1 if not set commonSLBM uint8 requiredLen int } func (v VLA) preprocessForMashaling(ctx *vlaMarshalingContext) error { //nolint:cyclop // Initialize indices to -1 (not set) for i := range ctx.slIndices { for j := range ctx.slIndices[i] { ctx.slIndices[i][j] = -1 } } for i := range v.ActiveSpatialLayer { sl := &v.ActiveSpatialLayer[i] if sl.RTPStreamID < 0 || sl.RTPStreamID >= v.RTPStreamCount { return fmt.Errorf("invalid RTP streamID %d:%w", sl.RTPStreamID, ErrVLAInvalidStreamID) } if sl.SpatialID < 0 || sl.SpatialID >= 4 { return fmt.Errorf("invalid spatial ID %d: %w", sl.SpatialID, ErrVLAInvalidSpatialID) } if len(sl.TargetBitrates) == 0 || len(sl.TargetBitrates) > 4 { return fmt.Errorf("invalid temporal layer count %d: %w", len(sl.TargetBitrates), ErrVLAInvalidTemporalLayer) } ctx.slMBs[sl.RTPStreamID] |= 1 << sl.SpatialID if ctx.slIndices[sl.RTPStreamID][sl.SpatialID] != -1 { return fmt.Errorf("duplicate spatial layer: %w", ErrVLADuplicateSpatialID) } ctx.slIndices[sl.RTPStreamID][sl.SpatialID] = i } return nil } func (v VLA) calcTargetBitratesSize(ctx *vlaMarshalingContext) { for rtpStreamID := 0; rtpStreamID < v.RTPStreamCount; rtpStreamID++ { for spatialID := 0; spatialID < 4; spatialID++ { if idx := ctx.slIndices[rtpStreamID][spatialID]; idx >= 0 { for _, kbps := range v.ActiveSpatialLayer[idx].TargetBitrates { ctx.requiredLen += leb128Size(uint(kbps)) //nolint:gosec } } } } } func (v VLA) analyzeVLAForMarshaling(ctx *vlaMarshalingContext) error { // Validate RTPStreamCount if v.RTPStreamCount <= 0 || v.RTPStreamCount > 4 { return ErrVLAInvalidStreamCount } // Validate RTPStreamID if v.RTPStreamID < 0 || v.RTPStreamID >= v.RTPStreamCount { return ErrVLAInvalidStreamID } err := v.preprocessForMashaling(ctx) if err != nil { return err } ctx.commonSLBM = commonSLBMValues(ctx.slMBs[:]) // RID, NS, sl_bm fields if ctx.commonSLBM != 0 { ctx.requiredLen = 1 } else { ctx.requiredLen = 3 } // #tl fields ctx.requiredLen += (len(v.ActiveSpatialLayer)-1)/4 + 1 v.calcTargetBitratesSize(ctx) if v.HasResolutionAndFramerate { ctx.requiredLen += len(v.ActiveSpatialLayer) * 5 } return nil } // MarshalSize returns the size needed to marshal the VLA. func (v VLA) MarshalSize() (int, error) { var ctx vlaMarshalingContext if err := v.analyzeVLAForMarshaling(&ctx); err != nil { return 0, err } return ctx.requiredLen, nil } // MarshalTo marshals the VLA to the given buffer. // Returns io.ErrShortBuffer if buf is too small. func (v VLA) MarshalTo(buf []byte) (int, error) { //nolint:cyclop,gocognit var ctx vlaMarshalingContext if err := v.analyzeVLAForMarshaling(&ctx); err != nil { return 0, err } if len(buf) < ctx.requiredLen { return 0, io.ErrShortBuffer } offset := 0 // RID, NS, sl_bm fields buf[offset] = byte(v.RTPStreamID<<6) | byte(v.RTPStreamCount-1)<<4 | ctx.commonSLBM if ctx.commonSLBM == 0 { offset++ for streamID := 0; streamID < v.RTPStreamCount; streamID++ { if streamID%2 == 0 { buf[offset+streamID/2] |= ctx.slMBs[streamID] << 4 } else { buf[offset+streamID/2] |= ctx.slMBs[streamID] } } offset += (v.RTPStreamCount - 1) / 2 } // #tl fields offset++ var temporalLayerIndex int for rtpStreamID := 0; rtpStreamID < v.RTPStreamCount; rtpStreamID++ { for spatialID := 0; spatialID < 4; spatialID++ { if idx := ctx.slIndices[rtpStreamID][spatialID]; idx >= 0 { if temporalLayerIndex >= 4 { temporalLayerIndex = 0 offset++ } buf[offset] |= byte(len(v.ActiveSpatialLayer[idx].TargetBitrates)-1) << (2 * (3 - temporalLayerIndex)) temporalLayerIndex++ } } } // Target bitrate fields offset++ for rtpStreamID := 0; rtpStreamID < v.RTPStreamCount; rtpStreamID++ { for spatialID := 0; spatialID < 4; spatialID++ { if idx := ctx.slIndices[rtpStreamID][spatialID]; idx >= 0 { for _, kbps := range v.ActiveSpatialLayer[idx].TargetBitrates { offset += writeLeb128To(buf[offset:], uint(kbps)) //nolint:gosec } } } } // Resolution & framerate fields if v.HasResolutionAndFramerate { for _, sl := range v.ActiveSpatialLayer { binary.BigEndian.PutUint16(buf[offset+0:], uint16(sl.Width-1)) //nolint:gosec binary.BigEndian.PutUint16(buf[offset+2:], uint16(sl.Height-1)) //nolint:gosec buf[offset+4] = byte(sl.Framerate) offset += 5 } } return ctx.requiredLen, nil } // Marshal encodes VLA into a byte slice. func (v VLA) Marshal() ([]byte, error) { size, err := v.MarshalSize() if err != nil { return nil, err } buf := make([]byte, size) _, err = v.MarshalTo(buf) if err != nil { return nil, err } return buf, nil } func commonSLBMValues(slMBs []uint8) uint8 { var common uint8 for i := 0; i < len(slMBs); i++ { if slMBs[i] == 0 { continue } if common == 0 { common = slMBs[i] continue } if slMBs[i] != common { return 0 } } return common } type vlaUnmarshalingContext struct { payload []byte offset int slBMField uint8 slBMs [4]uint8 } func (ctx *vlaUnmarshalingContext) checkRemainingLen(requiredLen int) bool { return len(ctx.payload)-ctx.offset >= requiredLen } func (v *VLA) unmarshalSpatialLayers(ctx *vlaUnmarshalingContext) error { if !ctx.checkRemainingLen(1) { return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) } v.RTPStreamID = int(ctx.payload[ctx.offset] >> 6 & 0b11) v.RTPStreamCount = int(ctx.payload[ctx.offset]>>4&0b11) + 1 // sl_bm fields ctx.slBMField = ctx.payload[ctx.offset] & 0b1111 ctx.offset++ if ctx.slBMField != 0 { for streamID := 0; streamID < v.RTPStreamCount; streamID++ { ctx.slBMs[streamID] = ctx.slBMField } } else { if !ctx.checkRemainingLen((v.RTPStreamCount-1)/2 + 1) { return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) } // slX_bm fields for streamID := 0; streamID < v.RTPStreamCount; streamID++ { var bm uint8 if streamID%2 == 0 { bm = ctx.payload[ctx.offset+streamID/2] >> 4 & 0b1111 } else { bm = ctx.payload[ctx.offset+streamID/2] & 0b1111 } ctx.slBMs[streamID] = bm } ctx.offset += 1 + (v.RTPStreamCount-1)/2 } return nil } func (v *VLA) unmarshalTemporalLayers(ctx *vlaUnmarshalingContext) error { // nolint: cyclop if !ctx.checkRemainingLen(1) { return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) } var temporalLayerIndex int for streamID := 0; streamID < v.RTPStreamCount; streamID++ { for spatialID := 0; spatialID < 4; spatialID++ { if ctx.slBMs[streamID]&(1<= 4 { temporalLayerIndex = 0 ctx.offset++ if !ctx.checkRemainingLen(1) { return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) } } tlCount := int(ctx.payload[ctx.offset]>>(2*(3-temporalLayerIndex))&0b11) + 1 temporalLayerIndex++ sl := SpatialLayer{ RTPStreamID: streamID, SpatialID: spatialID, TargetBitrates: make([]int, tlCount), } v.ActiveSpatialLayer = append(v.ActiveSpatialLayer, sl) } } ctx.offset++ // target bitrates for i, sl := range v.ActiveSpatialLayer { for j := range sl.TargetBitrates { kbps, n, err := obu.ReadLeb128(ctx.payload[ctx.offset:]) if err != nil { return err } in := int(n) // nolint: gosec if !ctx.checkRemainingLen(in) { return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) } v.ActiveSpatialLayer[i].TargetBitrates[j] = int(kbps) // nolint: gosec ctx.offset += in } } return nil } func (v *VLA) unmarshalResolutionAndFramerate(ctx *vlaUnmarshalingContext) error { if !ctx.checkRemainingLen(len(v.ActiveSpatialLayer) * 5) { return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) } v.HasResolutionAndFramerate = true for i := range v.ActiveSpatialLayer { v.ActiveSpatialLayer[i].Width = int(binary.BigEndian.Uint16(ctx.payload[ctx.offset+0:])) + 1 v.ActiveSpatialLayer[i].Height = int(binary.BigEndian.Uint16(ctx.payload[ctx.offset+2:])) + 1 v.ActiveSpatialLayer[i].Framerate = int(ctx.payload[ctx.offset+4]) ctx.offset += 5 } return nil } // Unmarshal decodes VLA from a byte slice. func (v *VLA) Unmarshal(payload []byte) (int, error) { ctx := &vlaUnmarshalingContext{ payload: payload, } err := v.unmarshalSpatialLayers(ctx) if err != nil { return ctx.offset, err } // #tl fields (build the list ActiveSpatialLayer at the same time) err = v.unmarshalTemporalLayers(ctx) if err != nil { return ctx.offset, err } if len(ctx.payload) == ctx.offset { return ctx.offset, nil } // resolution & framerate (optional) err = v.unmarshalResolutionAndFramerate(ctx) if err != nil { return ctx.offset, err } return ctx.offset, nil } // String makes VLA printable. func (v VLA) String() string { out := fmt.Sprintf("RID:%d,RTPStreamCount:%d", v.RTPStreamID, v.RTPStreamCount) var slOut []string for _, sl := range v.ActiveSpatialLayer { out2 := fmt.Sprintf("RTPStreamID:%d", sl.RTPStreamID) out2 += fmt.Sprintf(",TargetBitrates:%v", sl.TargetBitrates) if v.HasResolutionAndFramerate { out2 += fmt.Sprintf(",Resolution:(%d,%d)", sl.Width, sl.Height) out2 += fmt.Sprintf(",Framerate:%d", sl.Framerate) } slOut = append(slOut, out2) } out += fmt.Sprintf(",ActiveSpatialLayers:{%s}", strings.Join(slOut, ",")) return out } // leb128Size returns the number of bytes needed to encode a value as LEB128. func leb128Size(in uint) int { size := 1 for in >>= 7; in != 0; in >>= 7 { size++ } return size } // writeLeb128To writes a LEB128 encoded value to buf and returns bytes written. func writeLeb128To(buf []byte, in uint) int { for i := range buf { buf[i] = byte(in & 0x7f) in >>= 7 if in == 0 { return i + 1 } buf[i] |= 0x80 } return 0 } rtp-1.8.27/vlaextension_test.go000066400000000000000000000353051512154406600165350ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package rtp import ( "encoding/hex" "io" "testing" "github.com/stretchr/testify/assert" ) func TestVLAMarshal(t *testing.T) { t.Run("3 streams no resolution and framerate", func(t *testing.T) { vla := &VLA{ RTPStreamID: 0, RTPStreamCount: 3, ActiveSpatialLayer: []SpatialLayer{ { RTPStreamID: 0, SpatialID: 0, TargetBitrates: []int{150}, }, { RTPStreamID: 1, SpatialID: 0, TargetBitrates: []int{240, 400}, }, { RTPStreamID: 2, SpatialID: 0, TargetBitrates: []int{720, 1200}, }, }, } bytesActual, err := vla.Marshal() assert.NoError(t, err) bytesExpected, err := hex.DecodeString("21149601f0019003d005b009") assert.NoError(t, err) assert.Equal( t, bytesExpected, bytesActual, "expected %s, actual %s", hex.EncodeToString(bytesExpected), hex.EncodeToString(bytesActual), ) }) t.Run("3 streams with resolution and framerate", func(t *testing.T) { vla := &VLA{ RTPStreamID: 2, RTPStreamCount: 3, ActiveSpatialLayer: []SpatialLayer{ { RTPStreamID: 0, SpatialID: 0, TargetBitrates: []int{150}, Width: 320, Height: 180, Framerate: 30, }, { RTPStreamID: 1, SpatialID: 0, TargetBitrates: []int{240, 400}, Width: 640, Height: 360, Framerate: 30, }, { RTPStreamID: 2, SpatialID: 0, TargetBitrates: []int{720, 1200}, Width: 1280, Height: 720, Framerate: 30, }, }, HasResolutionAndFramerate: true, } bytesActual, err := vla.Marshal() assert.NoError(t, err) bytesExpected, err := hex.DecodeString("a1149601f0019003d005b009013f00b31e027f01671e04ff02cf1e") assert.NoError(t, err) assert.Equal( t, bytesExpected, bytesActual, "expected %s, actual %s", hex.EncodeToString(bytesExpected), hex.EncodeToString(bytesActual), ) }) t.Run("Negative RTPStreamCount", func(t *testing.T) { vla := &VLA{ RTPStreamID: 0, RTPStreamCount: -1, ActiveSpatialLayer: []SpatialLayer{}, } _, err := vla.Marshal() assert.ErrorIs(t, err, ErrVLAInvalidStreamCount) }) t.Run("RTPStreamCount too large", func(t *testing.T) { vla := &VLA{ RTPStreamID: 0, RTPStreamCount: 5, ActiveSpatialLayer: []SpatialLayer{{}, {}, {}, {}, {}}, } _, err := vla.Marshal() assert.ErrorIs(t, err, ErrVLAInvalidStreamCount) }) t.Run("Negative RTPStreamID", func(t *testing.T) { vla := &VLA{ RTPStreamID: -1, RTPStreamCount: 1, ActiveSpatialLayer: []SpatialLayer{{}}, } _, err := vla.Marshal() assert.ErrorIs(t, err, ErrVLAInvalidStreamID) }) t.Run("RTPStreamID to large", func(t *testing.T) { vla := &VLA{ RTPStreamID: 1, RTPStreamCount: 1, ActiveSpatialLayer: []SpatialLayer{{}}, } _, err := vla.Marshal() assert.ErrorIs(t, err, ErrVLAInvalidStreamID) }) t.Run("Invalid stream ID in the spatial layer", func(t *testing.T) { vla := &VLA{ RTPStreamID: 0, RTPStreamCount: 1, ActiveSpatialLayer: []SpatialLayer{{ RTPStreamID: -1, }}, } _, err := vla.Marshal() assert.ErrorIs(t, err, ErrVLAInvalidStreamID) vla = &VLA{ RTPStreamID: 0, RTPStreamCount: 1, ActiveSpatialLayer: []SpatialLayer{{ RTPStreamID: 1, }}, } _, err = vla.Marshal() assert.ErrorIs(t, err, ErrVLAInvalidStreamID) }) t.Run("Invalid spatial ID in the spatial layer", func(t *testing.T) { vla := &VLA{ RTPStreamID: 0, RTPStreamCount: 1, ActiveSpatialLayer: []SpatialLayer{{ RTPStreamID: 0, SpatialID: -1, }}, } _, err := vla.Marshal() assert.ErrorIs(t, err, ErrVLAInvalidSpatialID) vla = &VLA{ RTPStreamID: 0, RTPStreamCount: 1, ActiveSpatialLayer: []SpatialLayer{{ RTPStreamID: 0, SpatialID: 5, }}, } _, err = vla.Marshal() assert.ErrorIs(t, err, ErrVLAInvalidSpatialID) }) t.Run("Invalid temporal layer in the spatial layer", func(t *testing.T) { vla := &VLA{ RTPStreamID: 0, RTPStreamCount: 1, ActiveSpatialLayer: []SpatialLayer{{ RTPStreamID: 0, SpatialID: 0, TargetBitrates: []int{}, }}, } _, err := vla.Marshal() assert.ErrorIs(t, err, ErrVLAInvalidTemporalLayer) vla = &VLA{ RTPStreamID: 0, RTPStreamCount: 1, ActiveSpatialLayer: []SpatialLayer{{ RTPStreamID: 0, SpatialID: 0, TargetBitrates: []int{100, 200, 300, 400, 500}, }}, } _, err = vla.Marshal() assert.ErrorIs(t, err, ErrVLAInvalidTemporalLayer) }) t.Run("Duplicate spatial ID in the spatial layer", func(t *testing.T) { vla := &VLA{ RTPStreamID: 0, RTPStreamCount: 1, ActiveSpatialLayer: []SpatialLayer{{ RTPStreamID: 0, SpatialID: 0, TargetBitrates: []int{100}, }, { RTPStreamID: 0, SpatialID: 0, TargetBitrates: []int{200}, }}, } _, err := vla.Marshal() assert.ErrorIs(t, err, ErrVLADuplicateSpatialID) }) } func TestVLAUnmarshal(t *testing.T) { t.Run("3 streams no resolution and framerate", func(t *testing.T) { // two layer ("low", "high") b, err := hex.DecodeString("21149601f0019003d005b009") assert.NoError(t, err) vla := &VLA{} n, err := vla.Unmarshal(b) assert.NoError(t, err) assert.Equal(t, len(b), n) assert.Equal(t, 0, vla.RTPStreamID) assert.Equal(t, 3, vla.RTPStreamCount) assert.Equal(t, 3, len(vla.ActiveSpatialLayer)) assert.Equal(t, 0, vla.ActiveSpatialLayer[0].RTPStreamID) assert.Equal(t, 0, vla.ActiveSpatialLayer[0].SpatialID) assert.Equal(t, 1, len(vla.ActiveSpatialLayer[0].TargetBitrates)) assert.Equal(t, 150, vla.ActiveSpatialLayer[0].TargetBitrates[0]) assert.Equal(t, 1, vla.ActiveSpatialLayer[1].RTPStreamID) assert.Equal(t, 0, vla.ActiveSpatialLayer[1].SpatialID) assert.Equal(t, 2, len(vla.ActiveSpatialLayer[1].TargetBitrates)) assert.Equal(t, 240, vla.ActiveSpatialLayer[1].TargetBitrates[0]) assert.Equal(t, 400, vla.ActiveSpatialLayer[1].TargetBitrates[1]) assert.False(t, vla.HasResolutionAndFramerate) assert.Equal(t, 2, vla.ActiveSpatialLayer[2].RTPStreamID) assert.Equal(t, 0, vla.ActiveSpatialLayer[2].SpatialID) assert.Equal(t, 2, len(vla.ActiveSpatialLayer[2].TargetBitrates)) assert.Equal(t, 720, vla.ActiveSpatialLayer[2].TargetBitrates[0]) assert.Equal(t, 1200, vla.ActiveSpatialLayer[2].TargetBitrates[1]) }) t.Run("3 streams with resolution and framerate", func(t *testing.T) { b, err := hex.DecodeString("a1149601f0019003d005b009013f00b31e027f01671e04ff02cf1e") assert.NoError(t, err) vla := &VLA{} n, err := vla.Unmarshal(b) assert.NoError(t, err) assert.Equal(t, len(b), n) assert.Equal(t, 2, vla.RTPStreamID) assert.Equal(t, 3, vla.RTPStreamCount) assert.Equal(t, 0, vla.ActiveSpatialLayer[0].RTPStreamID) assert.Equal(t, 0, vla.ActiveSpatialLayer[0].SpatialID) assert.Equal(t, 1, len(vla.ActiveSpatialLayer[0].TargetBitrates)) assert.Equal(t, 150, vla.ActiveSpatialLayer[0].TargetBitrates[0]) assert.Equal(t, 1, vla.ActiveSpatialLayer[1].RTPStreamID) assert.Equal(t, 0, vla.ActiveSpatialLayer[1].SpatialID) assert.Equal(t, 2, len(vla.ActiveSpatialLayer[1].TargetBitrates)) assert.Equal(t, 240, vla.ActiveSpatialLayer[1].TargetBitrates[0]) assert.Equal(t, 400, vla.ActiveSpatialLayer[1].TargetBitrates[1]) assert.Equal(t, 2, vla.ActiveSpatialLayer[2].RTPStreamID) assert.Equal(t, 0, vla.ActiveSpatialLayer[2].SpatialID) assert.Equal(t, 2, len(vla.ActiveSpatialLayer[2].TargetBitrates)) assert.Equal(t, 720, vla.ActiveSpatialLayer[2].TargetBitrates[0]) assert.Equal(t, 1200, vla.ActiveSpatialLayer[2].TargetBitrates[1]) assert.True(t, vla.HasResolutionAndFramerate) assert.Equal(t, 320, vla.ActiveSpatialLayer[0].Width) assert.Equal(t, 180, vla.ActiveSpatialLayer[0].Height) assert.Equal(t, 30, vla.ActiveSpatialLayer[0].Framerate) assert.Equal(t, 640, vla.ActiveSpatialLayer[1].Width) assert.Equal(t, 360, vla.ActiveSpatialLayer[1].Height) assert.Equal(t, 30, vla.ActiveSpatialLayer[1].Framerate) assert.Equal(t, 1280, vla.ActiveSpatialLayer[2].Width) assert.Equal(t, 720, vla.ActiveSpatialLayer[2].Height) assert.Equal(t, 30, vla.ActiveSpatialLayer[2].Framerate) }) t.Run("2 streams", func(t *testing.T) { // two layer ("low", "high") b, err := hex.DecodeString("1110c801d005b009") assert.NoError(t, err) vla := &VLA{} n, err := vla.Unmarshal(b) assert.NoError(t, err) assert.Equal(t, len(b), n) assert.Equal(t, 0, vla.RTPStreamID) assert.Equal(t, 2, vla.RTPStreamCount) assert.Equal(t, 2, len(vla.ActiveSpatialLayer)) assert.Equal(t, 0, vla.ActiveSpatialLayer[0].RTPStreamID) assert.Equal(t, 0, vla.ActiveSpatialLayer[0].SpatialID) assert.Equal(t, 1, len(vla.ActiveSpatialLayer[0].TargetBitrates)) assert.Equal(t, 200, vla.ActiveSpatialLayer[0].TargetBitrates[0]) assert.Equal(t, 1, vla.ActiveSpatialLayer[1].RTPStreamID) assert.Equal(t, 0, vla.ActiveSpatialLayer[1].SpatialID) assert.Equal(t, 2, len(vla.ActiveSpatialLayer[1].TargetBitrates)) assert.Equal(t, 720, vla.ActiveSpatialLayer[1].TargetBitrates[0]) assert.Equal(t, 1200, vla.ActiveSpatialLayer[1].TargetBitrates[1]) assert.False(t, vla.HasResolutionAndFramerate) }) t.Run("3 streams mid paused with resolution and framerate", func(t *testing.T) { b, err := hex.DecodeString("601010109601d005b009013f00b31e04ff02cf1e") assert.NoError(t, err) vla := &VLA{} n, err := vla.Unmarshal(b) assert.NoError(t, err) assert.Equal(t, len(b), n) assert.Equal(t, 1, vla.RTPStreamID) assert.Equal(t, 3, vla.RTPStreamCount) assert.Equal(t, 0, vla.ActiveSpatialLayer[0].RTPStreamID) assert.Equal(t, 0, vla.ActiveSpatialLayer[0].SpatialID) assert.Equal(t, 1, len(vla.ActiveSpatialLayer[0].TargetBitrates)) assert.Equal(t, 150, vla.ActiveSpatialLayer[0].TargetBitrates[0]) assert.Equal(t, 2, vla.ActiveSpatialLayer[1].RTPStreamID) assert.Equal(t, 0, vla.ActiveSpatialLayer[1].SpatialID) assert.Equal(t, 2, len(vla.ActiveSpatialLayer[1].TargetBitrates)) assert.Equal(t, 720, vla.ActiveSpatialLayer[1].TargetBitrates[0]) assert.Equal(t, 1200, vla.ActiveSpatialLayer[1].TargetBitrates[1]) assert.True(t, vla.HasResolutionAndFramerate) assert.Equal(t, 320, vla.ActiveSpatialLayer[0].Width) assert.Equal(t, 180, vla.ActiveSpatialLayer[0].Height) assert.Equal(t, 30, vla.ActiveSpatialLayer[0].Framerate) assert.Equal(t, 1280, vla.ActiveSpatialLayer[1].Width) assert.Equal(t, 720, vla.ActiveSpatialLayer[1].Height) assert.Equal(t, 30, vla.ActiveSpatialLayer[1].Framerate) }) t.Run("extra 1", func(t *testing.T) { b, err := hex.DecodeString("a0001040ac02f403") assert.NoError(t, err) vla := &VLA{} n, err := vla.Unmarshal(b) assert.NoError(t, err) assert.Equal(t, len(b), n) }) t.Run("extra 2", func(t *testing.T) { b, err := hex.DecodeString("a00010409405cc08") assert.NoError(t, err) vla := &VLA{} n, err := vla.Unmarshal(b) assert.NoError(t, err) assert.Equal(t, len(b), n) }) } func TestVLAMarshalThenUnmarshal(t *testing.T) { t.Run("multiple spatial layers", func(t *testing.T) { var spatialLayers []SpatialLayer for streamID := 0; streamID < 3; streamID++ { for spatialID := 0; spatialID < 4; spatialID++ { spatialLayers = append(spatialLayers, SpatialLayer{ RTPStreamID: streamID, SpatialID: spatialID, TargetBitrates: []int{150, 200}, Width: 320, Height: 180, Framerate: 30, }) } } vla0 := &VLA{ RTPStreamID: 2, RTPStreamCount: 3, ActiveSpatialLayer: spatialLayers, HasResolutionAndFramerate: true, } b, err := vla0.Marshal() assert.NoError(t, err) vla1 := &VLA{} n, err := vla1.Unmarshal(b) assert.NoError(t, err) assert.Equal(t, len(b), n) assert.Equal(t, vla0, vla1) }) t.Run("different spatial layer bitmasks", func(t *testing.T) { var spatialLayers []SpatialLayer for streamID := 0; streamID < 4; streamID++ { for spatialID := 0; spatialID < streamID+1; spatialID++ { spatialLayers = append(spatialLayers, SpatialLayer{ RTPStreamID: streamID, SpatialID: spatialID, TargetBitrates: []int{150, 200}, Width: 320, Height: 180, Framerate: 30, }) } } vla0 := &VLA{ RTPStreamID: 0, RTPStreamCount: 4, ActiveSpatialLayer: spatialLayers, HasResolutionAndFramerate: true, } b, err := vla0.Marshal() assert.NoError(t, err) assert.Equal(t, byte(0x00), b[0]&0x0f, "expects sl_bm to be 0") assert.Equal(t, byte(0x13), b[1], "expects sl0_bm,sl1_bm to be b0001,b0011") assert.Equal(t, byte(0x7f), b[2], "expects sl1_bm,sl2_bm to be b0111,b1111") t.Logf("b: %s", hex.EncodeToString(b)) vla1 := &VLA{} n, err := vla1.Unmarshal(b) assert.NoError(t, err) assert.Equal(t, len(b), n) assert.Equal(t, vla0, vla1) }) } func FuzzVLAUnmarshal(f *testing.F) { f.Add([]byte{0}) f.Add([]byte("70")) f.Fuzz(func(t *testing.T, data []byte) { vla := &VLA{} _, err := vla.Unmarshal(data) if err != nil { t.Skip() // If the function returns an error, we skip the test case } }) } func TestVLAMarshalTo(t *testing.T) { vla := &VLA{ RTPStreamID: 0, RTPStreamCount: 3, ActiveSpatialLayer: []SpatialLayer{ {RTPStreamID: 0, SpatialID: 0, TargetBitrates: []int{150}}, {RTPStreamID: 1, SpatialID: 0, TargetBitrates: []int{240, 400}}, {RTPStreamID: 2, SpatialID: 0, TargetBitrates: []int{720, 1200}}, }, } size, err := vla.MarshalSize() assert.NoError(t, err) buf := make([]byte, size) n, err := vla.MarshalTo(buf) assert.NoError(t, err) assert.Equal(t, size, n) expected, _ := vla.Marshal() assert.Equal(t, expected, buf) _, err = vla.MarshalTo(nil) assert.ErrorIs(t, err, io.ErrShortBuffer) } //nolint:gochecknoglobals var ( vlaSink []byte vlaBuf = make([]byte, 256) vlaSinkInt int ) func BenchmarkVLA_Marshal(b *testing.B) { vla := &VLA{ RTPStreamID: 0, RTPStreamCount: 3, ActiveSpatialLayer: []SpatialLayer{ {RTPStreamID: 0, SpatialID: 0, TargetBitrates: []int{150}}, {RTPStreamID: 1, SpatialID: 0, TargetBitrates: []int{240, 400}}, {RTPStreamID: 2, SpatialID: 0, TargetBitrates: []int{720, 1200}}, }, } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { vlaSink, _ = vla.Marshal() } } func BenchmarkVLA_MarshalTo(b *testing.B) { vla := &VLA{ RTPStreamID: 0, RTPStreamCount: 3, ActiveSpatialLayer: []SpatialLayer{ {RTPStreamID: 0, SpatialID: 0, TargetBitrates: []int{150}}, {RTPStreamID: 1, SpatialID: 0, TargetBitrates: []int{240, 400}}, {RTPStreamID: 2, SpatialID: 0, TargetBitrates: []int{720, 1200}}, }, } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { vlaSinkInt, _ = vla.MarshalTo(vlaBuf) } }