直播:后台 JWT 推流、前台画中画;WebRTC 服务与 Nginx WebSocket 代理
Made-with: Cursor
This commit is contained in:
28
server/vendor/github.com/pion/sdp/v3/.gitignore
generated
vendored
Normal file
28
server/vendor/github.com/pion/sdp/v3/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
### JetBrains IDE ###
|
||||
#####################
|
||||
.idea/
|
||||
|
||||
### Emacs Temporary Files ###
|
||||
#############################
|
||||
*~
|
||||
|
||||
### Folders ###
|
||||
###############
|
||||
bin/
|
||||
vendor/
|
||||
node_modules/
|
||||
|
||||
### Files ###
|
||||
#############
|
||||
*.ivf
|
||||
*.ogg
|
||||
tags
|
||||
cover.out
|
||||
*.sw[poe]
|
||||
*.wasm
|
||||
examples/sfu-ws/cert.pem
|
||||
examples/sfu-ws/key.pem
|
||||
wasm_exec.js
|
||||
126
server/vendor/github.com/pion/sdp/v3/.golangci.yml
generated
vendored
Normal file
126
server/vendor/github.com/pion/sdp/v3/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
misspell:
|
||||
locale: US
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
forbidigo:
|
||||
forbid:
|
||||
- ^fmt.Print(f|ln)?$
|
||||
- ^log.(Panic|Fatal|Print)(f|ln)?$
|
||||
- ^os.Exit$
|
||||
- ^panic$
|
||||
- ^print(ln)?$
|
||||
|
||||
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
|
||||
- contextcheck # check the function whether use a non-inherited context
|
||||
- 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
|
||||
- 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
|
||||
- exportloopref # checks for pointers to enclosing loop variables
|
||||
- forbidigo # Forbids identifiers
|
||||
- forcetypeassert # finds forced type assertions
|
||||
- gci # Gci control golang package import order and make it always deterministic.
|
||||
- gochecknoglobals # Checks that no globals are present in Go code
|
||||
- gochecknoinits # Checks that no init functions 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
|
||||
- godox # Tool for detection of FIXME, TODO and other comment keywords
|
||||
- goerr113 # Golang linter to check the errors handling expressions
|
||||
- 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.
|
||||
- goheader # Checks is file header matches to pattern
|
||||
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
|
||||
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.
|
||||
- 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.
|
||||
- goprintffuncname # Checks that printf-like functions are named with `f` at the end
|
||||
- gosec # Inspects source code for security problems
|
||||
- gosimple # Linter for Go source code that specializes in simplifying a code
|
||||
- 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
|
||||
- misspell # Finds commonly misspelled English words in comments
|
||||
- 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.
|
||||
- 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
|
||||
- stylecheck # Stylecheck is a replacement for golint
|
||||
- tagliatelle # Checks the struct tags.
|
||||
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17
|
||||
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
|
||||
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
|
||||
- unconvert # Remove unnecessary type conversions
|
||||
- unparam # Reports unused function parameters
|
||||
- unused # Checks Go code for unused constants, variables, functions and types
|
||||
- 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
|
||||
- containedctx # containedctx is a linter that detects struct contained context.Context field
|
||||
- cyclop # checks function and package cyclomatic complexity
|
||||
- exhaustivestruct # Checks if all struct's fields are initialized
|
||||
- funlen # Tool for detection of long functions
|
||||
- gocyclo # Computes and checks the cyclomatic complexity of functions
|
||||
- godot # Check if comments end in a period
|
||||
- gomnd # An analyzer to detect magic numbers.
|
||||
- ifshort # Checks that your code uses short syntax for if-statements whenever possible
|
||||
- ireturn # Accept Interfaces, Return Concrete Types
|
||||
- lll # Reports long lines
|
||||
- maintidx # maintidx measures the maintainability index of each function.
|
||||
- makezero # Finds slice declarations with non-zero initial length
|
||||
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted
|
||||
- nakedret # Finds naked returns in functions greater than a specified function length
|
||||
- nestif # Reports deeply nested if statements
|
||||
- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
|
||||
- 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
|
||||
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
|
||||
- varnamelen # checks that the length of a variable's name matches its scope
|
||||
- wrapcheck # Checks that errors returned from external packages are wrapped
|
||||
- wsl # Whitespace Linter - Forces you to use empty lines!
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude-rules:
|
||||
# Allow complex tests and examples, better to be self contained
|
||||
- path: (examples|main\.go|_test\.go)
|
||||
linters:
|
||||
- forbidigo
|
||||
- gocognit
|
||||
|
||||
# Allow forbidden identifiers in CLI commands
|
||||
- path: cmd
|
||||
linters:
|
||||
- forbidigo
|
||||
|
||||
run:
|
||||
skip-dirs-use-default: false
|
||||
5
server/vendor/github.com/pion/sdp/v3/.goreleaser.yml
generated
vendored
Normal file
5
server/vendor/github.com/pion/sdp/v3/.goreleaser.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
builds:
|
||||
- skip: true
|
||||
9
server/vendor/github.com/pion/sdp/v3/LICENSE
generated
vendored
Normal file
9
server/vendor/github.com/pion/sdp/v3/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 The Pion community <https://pion.ly>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
35
server/vendor/github.com/pion/sdp/v3/README.md
generated
vendored
Normal file
35
server/vendor/github.com/pion/sdp/v3/README.md
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
<h1 align="center">
|
||||
<br>
|
||||
Pion SDP
|
||||
<br>
|
||||
</h1>
|
||||
<h4 align="center">A Go implementation of the SDP</h4>
|
||||
<p align="center">
|
||||
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-sdp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion SDP"></a>
|
||||
<a href="https://sourcegraph.com/github.com/pion/sdp?badge"><img src="https://sourcegraph.com/github.com/pion/sdp/-/badge.svg" alt="Sourcegraph Widget"></a>
|
||||
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
|
||||
<br>
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/sdp/test.yaml">
|
||||
<a href="https://pkg.go.dev/github.com/pion/sdp/v2"><img src="https://pkg.go.dev/badge/github.com/pion/sdp/v2.svg" alt="Go Reference"></a>
|
||||
<a href="https://codecov.io/gh/pion/sdp"><img src="https://codecov.io/gh/pion/sdp/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
||||
<a href="https://goreportcard.com/report/github.com/pion/sdp"><img src="https://goreportcard.com/badge/github.com/pion/sdp" alt="Go Report Card"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
||||
</p>
|
||||
<br>
|
||||
|
||||
### 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 [Slack](https://pion.ly/slack).
|
||||
|
||||
Follow the [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
|
||||
216
server/vendor/github.com/pion/sdp/v3/base_lexer.go
generated
vendored
Normal file
216
server/vendor/github.com/pion/sdp/v3/base_lexer.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var errDocumentStart = errors.New("already on document start")
|
||||
|
||||
type syntaxError struct {
|
||||
s string
|
||||
i int
|
||||
}
|
||||
|
||||
func (e syntaxError) Error() string {
|
||||
if e.i < 0 {
|
||||
e.i = 0
|
||||
}
|
||||
return fmt.Sprintf("sdp: syntax error at pos %d: %s", e.i, strconv.QuoteToASCII(e.s[e.i:e.i+1]))
|
||||
}
|
||||
|
||||
type baseLexer struct {
|
||||
value string
|
||||
pos int
|
||||
}
|
||||
|
||||
func (l baseLexer) syntaxError() error {
|
||||
return syntaxError{s: l.value, i: l.pos - 1}
|
||||
}
|
||||
|
||||
func (l *baseLexer) unreadByte() error {
|
||||
if l.pos <= 0 {
|
||||
return errDocumentStart
|
||||
}
|
||||
l.pos--
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *baseLexer) readByte() (byte, error) {
|
||||
if l.pos >= len(l.value) {
|
||||
return byte(0), io.EOF
|
||||
}
|
||||
ch := l.value[l.pos]
|
||||
l.pos++
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func (l *baseLexer) nextLine() error {
|
||||
for {
|
||||
ch, err := l.readByte()
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isNewline(ch) {
|
||||
return l.unreadByte()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *baseLexer) readWhitespace() error {
|
||||
for {
|
||||
ch, err := l.readByte()
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isWhitespace(ch) {
|
||||
return l.unreadByte()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *baseLexer) readUint64Field() (i uint64, err error) {
|
||||
for {
|
||||
ch, err := l.readByte()
|
||||
if errors.Is(err, io.EOF) && i > 0 {
|
||||
break
|
||||
} else if err != nil {
|
||||
return i, err
|
||||
}
|
||||
|
||||
if isNewline(ch) {
|
||||
if err := l.unreadByte(); err != nil {
|
||||
return i, err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if isWhitespace(ch) {
|
||||
if err := l.readWhitespace(); err != nil {
|
||||
return i, err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
switch ch {
|
||||
case '0':
|
||||
i *= 10
|
||||
case '1':
|
||||
i = i*10 + 1
|
||||
case '2':
|
||||
i = i*10 + 2
|
||||
case '3':
|
||||
i = i*10 + 3
|
||||
case '4':
|
||||
i = i*10 + 4
|
||||
case '5':
|
||||
i = i*10 + 5
|
||||
case '6':
|
||||
i = i*10 + 6
|
||||
case '7':
|
||||
i = i*10 + 7
|
||||
case '8':
|
||||
i = i*10 + 8
|
||||
case '9':
|
||||
i = i*10 + 9
|
||||
default:
|
||||
return i, l.syntaxError()
|
||||
}
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// Returns next field on this line or empty string if no more fields on line
|
||||
func (l *baseLexer) readField() (string, error) {
|
||||
start := l.pos
|
||||
var stop int
|
||||
for {
|
||||
stop = l.pos
|
||||
ch, err := l.readByte()
|
||||
if errors.Is(err, io.EOF) && stop > start {
|
||||
break
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if isNewline(ch) {
|
||||
if err := l.unreadByte(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if isWhitespace(ch) {
|
||||
if err := l.readWhitespace(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return l.value[start:stop], nil
|
||||
}
|
||||
|
||||
// Returns symbols until line end
|
||||
func (l *baseLexer) readLine() (string, error) {
|
||||
start := l.pos
|
||||
trim := 1
|
||||
for {
|
||||
ch, err := l.readByte()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ch == '\r' {
|
||||
trim++
|
||||
}
|
||||
if ch == '\n' {
|
||||
return l.value[start : l.pos-trim], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *baseLexer) readType() (byte, error) {
|
||||
for {
|
||||
firstByte, err := l.readByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if isNewline(firstByte) {
|
||||
continue
|
||||
}
|
||||
|
||||
secondByte, err := l.readByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if secondByte != '=' {
|
||||
return firstByte, l.syntaxError()
|
||||
}
|
||||
|
||||
return firstByte, nil
|
||||
}
|
||||
}
|
||||
|
||||
func isNewline(ch byte) bool { return ch == '\n' || ch == '\r' }
|
||||
|
||||
func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' }
|
||||
|
||||
func anyOf(element string, data ...string) bool {
|
||||
for _, v := range data {
|
||||
if element == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
22
server/vendor/github.com/pion/sdp/v3/codecov.yml
generated
vendored
Normal file
22
server/vendor/github.com/pion/sdp/v3/codecov.yml
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
#
|
||||
# DO NOT EDIT THIS FILE
|
||||
#
|
||||
# It is automatically copied from https://github.com/pion/.goassets repository.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
# Allow decreasing 2% of total coverage to avoid noise.
|
||||
threshold: 2%
|
||||
patch:
|
||||
default:
|
||||
target: 70%
|
||||
only_pulls: true
|
||||
|
||||
ignore:
|
||||
- "examples/*"
|
||||
- "examples/**/*"
|
||||
188
server/vendor/github.com/pion/sdp/v3/common_description.go
generated
vendored
Normal file
188
server/vendor/github.com/pion/sdp/v3/common_description.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Information describes the "i=" field which provides textual information
|
||||
// about the session.
|
||||
type Information string
|
||||
|
||||
func (i Information) String() string {
|
||||
return stringFromMarshal(i.marshalInto, i.marshalSize)
|
||||
}
|
||||
|
||||
func (i Information) marshalInto(b []byte) []byte {
|
||||
return append(b, i...)
|
||||
}
|
||||
|
||||
func (i Information) marshalSize() (size int) {
|
||||
return len(i)
|
||||
}
|
||||
|
||||
// ConnectionInformation defines the representation for the "c=" field
|
||||
// containing connection data.
|
||||
type ConnectionInformation struct {
|
||||
NetworkType string
|
||||
AddressType string
|
||||
Address *Address
|
||||
}
|
||||
|
||||
func (c ConnectionInformation) String() string {
|
||||
return stringFromMarshal(c.marshalInto, c.marshalSize)
|
||||
}
|
||||
|
||||
func (c ConnectionInformation) marshalInto(b []byte) []byte {
|
||||
b = append(append(b, c.NetworkType...), ' ')
|
||||
b = append(b, c.AddressType...)
|
||||
|
||||
if c.Address != nil {
|
||||
b = append(b, ' ')
|
||||
b = c.Address.marshalInto(b)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (c ConnectionInformation) marshalSize() (size int) {
|
||||
size = len(c.NetworkType)
|
||||
size += 1 + len(c.AddressType)
|
||||
if c.Address != nil {
|
||||
size += 1 + c.Address.marshalSize()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Address desribes a structured address token from within the "c=" field.
|
||||
type Address struct {
|
||||
Address string
|
||||
TTL *int
|
||||
Range *int
|
||||
}
|
||||
|
||||
func (c *Address) String() string {
|
||||
return stringFromMarshal(c.marshalInto, c.marshalSize)
|
||||
}
|
||||
|
||||
func (c *Address) marshalInto(b []byte) []byte {
|
||||
b = append(b, c.Address...)
|
||||
if c.TTL != nil {
|
||||
b = append(b, '/')
|
||||
b = strconv.AppendInt(b, int64(*c.TTL), 10)
|
||||
}
|
||||
if c.Range != nil {
|
||||
b = append(b, '/')
|
||||
b = strconv.AppendInt(b, int64(*c.Range), 10)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (c Address) marshalSize() (size int) {
|
||||
size = len(c.Address)
|
||||
if c.TTL != nil {
|
||||
size += 1 + lenUint(uint64(*c.TTL))
|
||||
}
|
||||
if c.Range != nil {
|
||||
size += 1 + lenUint(uint64(*c.Range))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Bandwidth describes an optional field which denotes the proposed bandwidth
|
||||
// to be used by the session or media.
|
||||
type Bandwidth struct {
|
||||
Experimental bool
|
||||
Type string
|
||||
Bandwidth uint64
|
||||
}
|
||||
|
||||
func (b Bandwidth) String() string {
|
||||
return stringFromMarshal(b.marshalInto, b.marshalSize)
|
||||
}
|
||||
|
||||
func (b Bandwidth) marshalInto(d []byte) []byte {
|
||||
if b.Experimental {
|
||||
d = append(d, "X-"...)
|
||||
}
|
||||
d = append(append(d, b.Type...), ':')
|
||||
return strconv.AppendUint(d, b.Bandwidth, 10)
|
||||
}
|
||||
|
||||
func (b Bandwidth) marshalSize() (size int) {
|
||||
if b.Experimental {
|
||||
size += 2
|
||||
}
|
||||
|
||||
size += len(b.Type) + 1 + lenUint(b.Bandwidth)
|
||||
return
|
||||
}
|
||||
|
||||
// EncryptionKey describes the "k=" which conveys encryption key information.
|
||||
type EncryptionKey string
|
||||
|
||||
func (e EncryptionKey) String() string {
|
||||
return stringFromMarshal(e.marshalInto, e.marshalSize)
|
||||
}
|
||||
|
||||
func (e EncryptionKey) marshalInto(b []byte) []byte {
|
||||
return append(b, e...)
|
||||
}
|
||||
|
||||
func (e EncryptionKey) marshalSize() (size int) {
|
||||
return len(e)
|
||||
}
|
||||
|
||||
// Attribute describes the "a=" field which represents the primary means for
|
||||
// extending SDP.
|
||||
type Attribute struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// NewPropertyAttribute constructs a new attribute
|
||||
func NewPropertyAttribute(key string) Attribute {
|
||||
return Attribute{
|
||||
Key: key,
|
||||
}
|
||||
}
|
||||
|
||||
// NewAttribute constructs a new attribute
|
||||
func NewAttribute(key, value string) Attribute {
|
||||
return Attribute{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func (a Attribute) String() string {
|
||||
return stringFromMarshal(a.marshalInto, a.marshalSize)
|
||||
}
|
||||
|
||||
func (a Attribute) marshalInto(b []byte) []byte {
|
||||
b = append(b, a.Key...)
|
||||
if len(a.Value) > 0 {
|
||||
b = append(append(b, ':'), a.Value...)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (a Attribute) marshalSize() (size int) {
|
||||
size = len(a.Key)
|
||||
if len(a.Value) > 0 {
|
||||
size += 1 + len(a.Value)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
// IsICECandidate returns true if the attribute key equals "candidate".
|
||||
func (a Attribute) IsICECandidate() bool {
|
||||
return a.Key == "candidate"
|
||||
}
|
||||
62
server/vendor/github.com/pion/sdp/v3/direction.go
generated
vendored
Normal file
62
server/vendor/github.com/pion/sdp/v3/direction.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
import "errors"
|
||||
|
||||
// Direction is a marker for transmission directon of an endpoint
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
// DirectionSendRecv is for bidirectional communication
|
||||
DirectionSendRecv Direction = iota + 1
|
||||
// DirectionSendOnly is for outgoing communication
|
||||
DirectionSendOnly
|
||||
// DirectionRecvOnly is for incoming communication
|
||||
DirectionRecvOnly
|
||||
// DirectionInactive is for no communication
|
||||
DirectionInactive
|
||||
)
|
||||
|
||||
const (
|
||||
directionSendRecvStr = "sendrecv"
|
||||
directionSendOnlyStr = "sendonly"
|
||||
directionRecvOnlyStr = "recvonly"
|
||||
directionInactiveStr = "inactive"
|
||||
directionUnknownStr = ""
|
||||
)
|
||||
|
||||
var errDirectionString = errors.New("invalid direction string")
|
||||
|
||||
// NewDirection defines a procedure for creating a new direction from a raw
|
||||
// string.
|
||||
func NewDirection(raw string) (Direction, error) {
|
||||
switch raw {
|
||||
case directionSendRecvStr:
|
||||
return DirectionSendRecv, nil
|
||||
case directionSendOnlyStr:
|
||||
return DirectionSendOnly, nil
|
||||
case directionRecvOnlyStr:
|
||||
return DirectionRecvOnly, nil
|
||||
case directionInactiveStr:
|
||||
return DirectionInactive, nil
|
||||
default:
|
||||
return Direction(unknown), errDirectionString
|
||||
}
|
||||
}
|
||||
|
||||
func (t Direction) String() string {
|
||||
switch t {
|
||||
case DirectionSendRecv:
|
||||
return directionSendRecvStr
|
||||
case DirectionSendOnly:
|
||||
return directionSendOnlyStr
|
||||
case DirectionRecvOnly:
|
||||
return directionRecvOnlyStr
|
||||
case DirectionInactive:
|
||||
return directionInactiveStr
|
||||
default:
|
||||
return directionUnknownStr
|
||||
}
|
||||
}
|
||||
111
server/vendor/github.com/pion/sdp/v3/extmap.go
generated
vendored
Normal file
111
server/vendor/github.com/pion/sdp/v3/extmap.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Default ext values
|
||||
const (
|
||||
DefExtMapValueABSSendTime = 1
|
||||
DefExtMapValueTransportCC = 2
|
||||
DefExtMapValueSDESMid = 3
|
||||
DefExtMapValueSDESRTPStreamID = 4
|
||||
|
||||
ABSSendTimeURI = "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"
|
||||
TransportCCURI = "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
|
||||
SDESMidURI = "urn:ietf:params:rtp-hdrext:sdes:mid"
|
||||
SDESRTPStreamIDURI = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"
|
||||
AudioLevelURI = "urn:ietf:params:rtp-hdrext:ssrc-audio-level"
|
||||
)
|
||||
|
||||
// ExtMap represents the activation of a single RTP header extension
|
||||
type ExtMap struct {
|
||||
Value int
|
||||
Direction Direction
|
||||
URI *url.URL
|
||||
ExtAttr *string
|
||||
}
|
||||
|
||||
// Clone converts this object to an Attribute
|
||||
func (e *ExtMap) Clone() Attribute {
|
||||
return Attribute{Key: "extmap", Value: e.string()}
|
||||
}
|
||||
|
||||
// Unmarshal creates an Extmap from a string
|
||||
func (e *ExtMap) Unmarshal(raw string) error {
|
||||
parts := strings.SplitN(raw, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("%w: %v", errSyntaxError, raw)
|
||||
}
|
||||
|
||||
fields := strings.Fields(parts[1])
|
||||
if len(fields) < 2 {
|
||||
return fmt.Errorf("%w: %v", errSyntaxError, raw)
|
||||
}
|
||||
|
||||
valdir := strings.Split(fields[0], "/")
|
||||
value, err := strconv.ParseInt(valdir[0], 10, 64)
|
||||
if (value < 1) || (value > 246) {
|
||||
return fmt.Errorf("%w: %v -- extmap key must be in the range 1-256", errSyntaxError, valdir[0])
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", errSyntaxError, valdir[0])
|
||||
}
|
||||
|
||||
var direction Direction
|
||||
if len(valdir) == 2 {
|
||||
direction, err = NewDirection(valdir[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
uri, err := url.Parse(fields[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(fields) == 3 {
|
||||
tmp := fields[2]
|
||||
e.ExtAttr = &tmp
|
||||
}
|
||||
|
||||
e.Value = int(value)
|
||||
e.Direction = direction
|
||||
e.URI = uri
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal creates a string from an ExtMap
|
||||
func (e *ExtMap) Marshal() string {
|
||||
return e.Name() + ":" + e.string()
|
||||
}
|
||||
|
||||
func (e *ExtMap) string() string {
|
||||
output := fmt.Sprintf("%d", e.Value)
|
||||
dirstring := e.Direction.String()
|
||||
if dirstring != directionUnknownStr {
|
||||
output += "/" + dirstring
|
||||
}
|
||||
|
||||
if e.URI != nil {
|
||||
output += " " + e.URI.String()
|
||||
}
|
||||
|
||||
if e.ExtAttr != nil {
|
||||
output += " " + *e.ExtAttr
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// Name returns the constant name of this object
|
||||
func (e *ExtMap) Name() string {
|
||||
return "extmap"
|
||||
}
|
||||
210
server/vendor/github.com/pion/sdp/v3/jsep.go
generated
vendored
Normal file
210
server/vendor/github.com/pion/sdp/v3/jsep.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Constants for SDP attributes used in JSEP
|
||||
const (
|
||||
AttrKeyCandidate = "candidate"
|
||||
AttrKeyEndOfCandidates = "end-of-candidates"
|
||||
AttrKeyIdentity = "identity"
|
||||
AttrKeyGroup = "group"
|
||||
AttrKeySSRC = "ssrc"
|
||||
AttrKeySSRCGroup = "ssrc-group"
|
||||
AttrKeyMsid = "msid"
|
||||
AttrKeyMsidSemantic = "msid-semantic"
|
||||
AttrKeyConnectionSetup = "setup"
|
||||
AttrKeyMID = "mid"
|
||||
AttrKeyICELite = "ice-lite"
|
||||
AttrKeyRTCPMux = "rtcp-mux"
|
||||
AttrKeyRTCPRsize = "rtcp-rsize"
|
||||
AttrKeyInactive = "inactive"
|
||||
AttrKeyRecvOnly = "recvonly"
|
||||
AttrKeySendOnly = "sendonly"
|
||||
AttrKeySendRecv = "sendrecv"
|
||||
AttrKeyExtMap = "extmap"
|
||||
AttrKeyExtMapAllowMixed = "extmap-allow-mixed"
|
||||
)
|
||||
|
||||
// Constants for semantic tokens used in JSEP
|
||||
const (
|
||||
SemanticTokenLipSynchronization = "LS"
|
||||
SemanticTokenFlowIdentification = "FID"
|
||||
SemanticTokenForwardErrorCorrection = "FEC"
|
||||
SemanticTokenWebRTCMediaStreams = "WMS"
|
||||
)
|
||||
|
||||
// Constants for extmap key
|
||||
const (
|
||||
ExtMapValueTransportCC = 3
|
||||
)
|
||||
|
||||
func extMapURI() map[int]string {
|
||||
return map[int]string{
|
||||
ExtMapValueTransportCC: "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01",
|
||||
}
|
||||
}
|
||||
|
||||
// API to match draft-ietf-rtcweb-jsep
|
||||
// Move to webrtc or its own package?
|
||||
|
||||
// NewJSEPSessionDescription creates a new SessionDescription with
|
||||
// some settings that are required by the JSEP spec.
|
||||
//
|
||||
// Note: Since v2.4.0, session ID has been fixed to use crypto random according to
|
||||
//
|
||||
// JSEP spec, so that NewJSEPSessionDescription now returns error as a second
|
||||
// return value.
|
||||
func NewJSEPSessionDescription(identity bool) (*SessionDescription, error) {
|
||||
sid, err := newSessionID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &SessionDescription{
|
||||
Version: 0,
|
||||
Origin: Origin{
|
||||
Username: "-",
|
||||
SessionID: sid,
|
||||
SessionVersion: uint64(time.Now().Unix()),
|
||||
NetworkType: "IN",
|
||||
AddressType: "IP4",
|
||||
UnicastAddress: "0.0.0.0",
|
||||
},
|
||||
SessionName: "-",
|
||||
TimeDescriptions: []TimeDescription{
|
||||
{
|
||||
Timing: Timing{
|
||||
StartTime: 0,
|
||||
StopTime: 0,
|
||||
},
|
||||
RepeatTimes: nil,
|
||||
},
|
||||
},
|
||||
Attributes: []Attribute{
|
||||
// "Attribute(ice-options:trickle)", // TODO: implement trickle ICE
|
||||
},
|
||||
}
|
||||
|
||||
if identity {
|
||||
d.WithPropertyAttribute(AttrKeyIdentity)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// WithPropertyAttribute adds a property attribute 'a=key' to the session description
|
||||
func (s *SessionDescription) WithPropertyAttribute(key string) *SessionDescription {
|
||||
s.Attributes = append(s.Attributes, NewPropertyAttribute(key))
|
||||
return s
|
||||
}
|
||||
|
||||
// WithValueAttribute adds a value attribute 'a=key:value' to the session description
|
||||
func (s *SessionDescription) WithValueAttribute(key, value string) *SessionDescription {
|
||||
s.Attributes = append(s.Attributes, NewAttribute(key, value))
|
||||
return s
|
||||
}
|
||||
|
||||
// WithFingerprint adds a fingerprint to the session description
|
||||
func (s *SessionDescription) WithFingerprint(algorithm, value string) *SessionDescription {
|
||||
return s.WithValueAttribute("fingerprint", algorithm+" "+value)
|
||||
}
|
||||
|
||||
// WithMedia adds a media description to the session description
|
||||
func (s *SessionDescription) WithMedia(md *MediaDescription) *SessionDescription {
|
||||
s.MediaDescriptions = append(s.MediaDescriptions, md)
|
||||
return s
|
||||
}
|
||||
|
||||
// NewJSEPMediaDescription creates a new MediaName with
|
||||
// some settings that are required by the JSEP spec.
|
||||
func NewJSEPMediaDescription(codecType string, _ []string) *MediaDescription {
|
||||
return &MediaDescription{
|
||||
MediaName: MediaName{
|
||||
Media: codecType,
|
||||
Port: RangedPort{Value: 9},
|
||||
Protos: []string{"UDP", "TLS", "RTP", "SAVPF"},
|
||||
},
|
||||
ConnectionInformation: &ConnectionInformation{
|
||||
NetworkType: "IN",
|
||||
AddressType: "IP4",
|
||||
Address: &Address{
|
||||
Address: "0.0.0.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithPropertyAttribute adds a property attribute 'a=key' to the media description
|
||||
func (d *MediaDescription) WithPropertyAttribute(key string) *MediaDescription {
|
||||
d.Attributes = append(d.Attributes, NewPropertyAttribute(key))
|
||||
return d
|
||||
}
|
||||
|
||||
// WithValueAttribute adds a value attribute 'a=key:value' to the media description
|
||||
func (d *MediaDescription) WithValueAttribute(key, value string) *MediaDescription {
|
||||
d.Attributes = append(d.Attributes, NewAttribute(key, value))
|
||||
return d
|
||||
}
|
||||
|
||||
// WithFingerprint adds a fingerprint to the media description
|
||||
func (d *MediaDescription) WithFingerprint(algorithm, value string) *MediaDescription {
|
||||
return d.WithValueAttribute("fingerprint", algorithm+" "+value)
|
||||
}
|
||||
|
||||
// WithICECredentials adds ICE credentials to the media description
|
||||
func (d *MediaDescription) WithICECredentials(username, password string) *MediaDescription {
|
||||
return d.
|
||||
WithValueAttribute("ice-ufrag", username).
|
||||
WithValueAttribute("ice-pwd", password)
|
||||
}
|
||||
|
||||
// WithCodec adds codec information to the media description
|
||||
func (d *MediaDescription) WithCodec(payloadType uint8, name string, clockrate uint32, channels uint16, fmtp string) *MediaDescription {
|
||||
d.MediaName.Formats = append(d.MediaName.Formats, strconv.Itoa(int(payloadType)))
|
||||
rtpmap := fmt.Sprintf("%d %s/%d", payloadType, name, clockrate)
|
||||
if channels > 0 {
|
||||
rtpmap += fmt.Sprintf("/%d", channels)
|
||||
}
|
||||
d.WithValueAttribute("rtpmap", rtpmap)
|
||||
if fmtp != "" {
|
||||
d.WithValueAttribute("fmtp", fmt.Sprintf("%d %s", payloadType, fmtp))
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// WithMediaSource adds media source information to the media description
|
||||
func (d *MediaDescription) WithMediaSource(ssrc uint32, cname, streamLabel, label string) *MediaDescription {
|
||||
return d.
|
||||
WithValueAttribute("ssrc", fmt.Sprintf("%d cname:%s", ssrc, cname)). // Deprecated but not phased out?
|
||||
WithValueAttribute("ssrc", fmt.Sprintf("%d msid:%s %s", ssrc, streamLabel, label)).
|
||||
WithValueAttribute("ssrc", fmt.Sprintf("%d mslabel:%s", ssrc, streamLabel)). // Deprecated but not phased out?
|
||||
WithValueAttribute("ssrc", fmt.Sprintf("%d label:%s", ssrc, label)) // Deprecated but not phased out?
|
||||
}
|
||||
|
||||
// WithCandidate adds an ICE candidate to the media description
|
||||
// Deprecated: use WithICECandidate instead
|
||||
func (d *MediaDescription) WithCandidate(value string) *MediaDescription {
|
||||
return d.WithValueAttribute("candidate", value)
|
||||
}
|
||||
|
||||
// WithExtMap adds an extmap to the media description
|
||||
func (d *MediaDescription) WithExtMap(e ExtMap) *MediaDescription {
|
||||
return d.WithPropertyAttribute(e.Marshal())
|
||||
}
|
||||
|
||||
// WithTransportCCExtMap adds an extmap to the media description
|
||||
func (d *MediaDescription) WithTransportCCExtMap() *MediaDescription {
|
||||
uri, _ := url.Parse(extMapURI()[ExtMapValueTransportCC])
|
||||
e := ExtMap{
|
||||
Value: ExtMapValueTransportCC,
|
||||
URI: uri,
|
||||
}
|
||||
return d.WithExtMap(e)
|
||||
}
|
||||
240
server/vendor/github.com/pion/sdp/v3/marshal.go
generated
vendored
Normal file
240
server/vendor/github.com/pion/sdp/v3/marshal.go
generated
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
// Marshal takes a SDP struct to text
|
||||
// https://tools.ietf.org/html/rfc4566#section-5
|
||||
// Session description
|
||||
//
|
||||
// v= (protocol version)
|
||||
// o= (originator and session identifier)
|
||||
// s= (session name)
|
||||
// i=* (session information)
|
||||
// u=* (URI of description)
|
||||
// e=* (email address)
|
||||
// p=* (phone number)
|
||||
// c=* (connection information -- not required if included in
|
||||
// all media)
|
||||
// b=* (zero or more bandwidth information lines)
|
||||
// One or more time descriptions ("t=" and "r=" lines; see below)
|
||||
// z=* (time zone adjustments)
|
||||
// k=* (encryption key)
|
||||
// a=* (zero or more session attribute lines)
|
||||
// Zero or more media descriptions
|
||||
//
|
||||
// Time description
|
||||
//
|
||||
// t= (time the session is active)
|
||||
// r=* (zero or more repeat times)
|
||||
//
|
||||
// Media description, if present
|
||||
//
|
||||
// m= (media name and transport address)
|
||||
// i=* (media title)
|
||||
// c=* (connection information -- optional if included at
|
||||
// session level)
|
||||
// b=* (zero or more bandwidth information lines)
|
||||
// k=* (encryption key)
|
||||
// a=* (zero or more media attribute lines)
|
||||
func (s *SessionDescription) Marshal() ([]byte, error) {
|
||||
m := make(marshaller, 0, s.MarshalSize())
|
||||
|
||||
m.addKeyValue("v=", s.Version.marshalInto)
|
||||
m.addKeyValue("o=", s.Origin.marshalInto)
|
||||
m.addKeyValue("s=", s.SessionName.marshalInto)
|
||||
|
||||
if s.SessionInformation != nil {
|
||||
m.addKeyValue("i=", s.SessionInformation.marshalInto)
|
||||
}
|
||||
|
||||
if s.URI != nil {
|
||||
m = append(m, "u="...)
|
||||
m = append(m, s.URI.String()...)
|
||||
m = append(m, "\r\n"...)
|
||||
}
|
||||
|
||||
if s.EmailAddress != nil {
|
||||
m.addKeyValue("e=", s.EmailAddress.marshalInto)
|
||||
}
|
||||
|
||||
if s.PhoneNumber != nil {
|
||||
m.addKeyValue("p=", s.PhoneNumber.marshalInto)
|
||||
}
|
||||
|
||||
if s.ConnectionInformation != nil {
|
||||
m.addKeyValue("c=", s.ConnectionInformation.marshalInto)
|
||||
}
|
||||
|
||||
for _, b := range s.Bandwidth {
|
||||
m.addKeyValue("b=", b.marshalInto)
|
||||
}
|
||||
|
||||
for _, td := range s.TimeDescriptions {
|
||||
m.addKeyValue("t=", td.Timing.marshalInto)
|
||||
for _, r := range td.RepeatTimes {
|
||||
m.addKeyValue("r=", r.marshalInto)
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.TimeZones) > 0 {
|
||||
m = append(m, "z="...)
|
||||
for i, z := range s.TimeZones {
|
||||
if i > 0 {
|
||||
m = append(m, ' ')
|
||||
}
|
||||
m = z.marshalInto(m)
|
||||
}
|
||||
m = append(m, "\r\n"...)
|
||||
}
|
||||
|
||||
if s.EncryptionKey != nil {
|
||||
m.addKeyValue("k=", s.EncryptionKey.marshalInto)
|
||||
}
|
||||
|
||||
for _, a := range s.Attributes {
|
||||
m.addKeyValue("a=", a.marshalInto)
|
||||
}
|
||||
|
||||
for _, md := range s.MediaDescriptions {
|
||||
m.addKeyValue("m=", md.MediaName.marshalInto)
|
||||
|
||||
if md.MediaTitle != nil {
|
||||
m.addKeyValue("i=", md.MediaTitle.marshalInto)
|
||||
}
|
||||
|
||||
if md.ConnectionInformation != nil {
|
||||
m.addKeyValue("c=", md.ConnectionInformation.marshalInto)
|
||||
}
|
||||
|
||||
for _, b := range md.Bandwidth {
|
||||
m.addKeyValue("b=", b.marshalInto)
|
||||
}
|
||||
|
||||
if md.EncryptionKey != nil {
|
||||
m.addKeyValue("k=", md.EncryptionKey.marshalInto)
|
||||
}
|
||||
|
||||
for _, a := range md.Attributes {
|
||||
m.addKeyValue("a=", a.marshalInto)
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// `$type=` and CRLF size
|
||||
const lineBaseSize = 4
|
||||
|
||||
// MarshalSize returns the size of the SessionDescription once marshaled.
|
||||
func (s *SessionDescription) MarshalSize() (marshalSize int) {
|
||||
marshalSize += lineBaseSize + s.Version.marshalSize()
|
||||
marshalSize += lineBaseSize + s.Origin.marshalSize()
|
||||
marshalSize += lineBaseSize + s.SessionName.marshalSize()
|
||||
|
||||
if s.SessionInformation != nil {
|
||||
marshalSize += lineBaseSize + s.SessionInformation.marshalSize()
|
||||
}
|
||||
|
||||
if s.URI != nil {
|
||||
marshalSize += lineBaseSize + len(s.URI.String())
|
||||
}
|
||||
|
||||
if s.EmailAddress != nil {
|
||||
marshalSize += lineBaseSize + s.EmailAddress.marshalSize()
|
||||
}
|
||||
|
||||
if s.PhoneNumber != nil {
|
||||
marshalSize += lineBaseSize + s.PhoneNumber.marshalSize()
|
||||
}
|
||||
|
||||
if s.ConnectionInformation != nil {
|
||||
marshalSize += lineBaseSize + s.ConnectionInformation.marshalSize()
|
||||
}
|
||||
|
||||
for _, b := range s.Bandwidth {
|
||||
marshalSize += lineBaseSize + b.marshalSize()
|
||||
}
|
||||
|
||||
for _, td := range s.TimeDescriptions {
|
||||
marshalSize += lineBaseSize + td.Timing.marshalSize()
|
||||
for _, r := range td.RepeatTimes {
|
||||
marshalSize += lineBaseSize + r.marshalSize()
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.TimeZones) > 0 {
|
||||
marshalSize += lineBaseSize
|
||||
|
||||
for i, z := range s.TimeZones {
|
||||
if i > 0 {
|
||||
marshalSize++
|
||||
}
|
||||
marshalSize += z.marshalSize()
|
||||
}
|
||||
}
|
||||
|
||||
if s.EncryptionKey != nil {
|
||||
marshalSize += lineBaseSize + s.EncryptionKey.marshalSize()
|
||||
}
|
||||
|
||||
for _, a := range s.Attributes {
|
||||
marshalSize += lineBaseSize + a.marshalSize()
|
||||
}
|
||||
|
||||
for _, md := range s.MediaDescriptions {
|
||||
marshalSize += lineBaseSize + md.MediaName.marshalSize()
|
||||
if md.MediaTitle != nil {
|
||||
marshalSize += lineBaseSize + md.MediaTitle.marshalSize()
|
||||
}
|
||||
if md.ConnectionInformation != nil {
|
||||
marshalSize += lineBaseSize + md.ConnectionInformation.marshalSize()
|
||||
}
|
||||
|
||||
for _, b := range md.Bandwidth {
|
||||
marshalSize += lineBaseSize + b.marshalSize()
|
||||
}
|
||||
|
||||
if md.EncryptionKey != nil {
|
||||
marshalSize += lineBaseSize + md.EncryptionKey.marshalSize()
|
||||
}
|
||||
|
||||
for _, a := range md.Attributes {
|
||||
marshalSize += lineBaseSize + a.marshalSize()
|
||||
}
|
||||
}
|
||||
|
||||
return marshalSize
|
||||
}
|
||||
|
||||
// marshaller contains state during marshaling.
|
||||
type marshaller []byte
|
||||
|
||||
func (m *marshaller) addKeyValue(key string, value func([]byte) []byte) {
|
||||
*m = append(*m, key...)
|
||||
*m = value(*m)
|
||||
*m = append(*m, "\r\n"...)
|
||||
}
|
||||
|
||||
func lenUint(i uint64) (count int) {
|
||||
if i == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
for i != 0 {
|
||||
i /= 10
|
||||
count++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func lenInt(i int64) (count int) {
|
||||
if i < 0 {
|
||||
return lenUint(uint64(-i)) + 1
|
||||
}
|
||||
return lenUint(uint64(i))
|
||||
}
|
||||
|
||||
func stringFromMarshal(marshalFunc func([]byte) []byte, sizeFunc func() int) string {
|
||||
return string(marshalFunc(make([]byte, 0, sizeFunc())))
|
||||
}
|
||||
128
server/vendor/github.com/pion/sdp/v3/media_description.go
generated
vendored
Normal file
128
server/vendor/github.com/pion/sdp/v3/media_description.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// MediaDescription represents a media type.
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.14
|
||||
type MediaDescription struct {
|
||||
// m=<media> <port>/<number of ports> <proto> <fmt> ...
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.14
|
||||
MediaName MediaName
|
||||
|
||||
// i=<session description>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.4
|
||||
MediaTitle *Information
|
||||
|
||||
// c=<nettype> <addrtype> <connection-address>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.7
|
||||
ConnectionInformation *ConnectionInformation
|
||||
|
||||
// b=<bwtype>:<bandwidth>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.8
|
||||
Bandwidth []Bandwidth
|
||||
|
||||
// k=<method>
|
||||
// k=<method>:<encryption key>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.12
|
||||
EncryptionKey *EncryptionKey
|
||||
|
||||
// a=<attribute>
|
||||
// a=<attribute>:<value>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.13
|
||||
Attributes []Attribute
|
||||
}
|
||||
|
||||
// Attribute returns the value of an attribute and if it exists
|
||||
func (d *MediaDescription) Attribute(key string) (string, bool) {
|
||||
for _, a := range d.Attributes {
|
||||
if a.Key == key {
|
||||
return a.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// RangedPort supports special format for the media field "m=" port value. If
|
||||
// it may be necessary to specify multiple transport ports, the protocol allows
|
||||
// to write it as: <port>/<number of ports> where number of ports is a an
|
||||
// offsetting range.
|
||||
type RangedPort struct {
|
||||
Value int
|
||||
Range *int
|
||||
}
|
||||
|
||||
func (p *RangedPort) String() string {
|
||||
output := strconv.Itoa(p.Value)
|
||||
if p.Range != nil {
|
||||
output += "/" + strconv.Itoa(*p.Range)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func (p RangedPort) marshalInto(b []byte) []byte {
|
||||
b = strconv.AppendInt(b, int64(p.Value), 10)
|
||||
if p.Range != nil {
|
||||
b = append(b, '/')
|
||||
b = strconv.AppendInt(b, int64(*p.Range), 10)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (p RangedPort) marshalSize() (size int) {
|
||||
size = lenInt(int64(p.Value))
|
||||
if p.Range != nil {
|
||||
size += 1 + lenInt(int64(*p.Range))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MediaName describes the "m=" field storage structure.
|
||||
type MediaName struct {
|
||||
Media string
|
||||
Port RangedPort
|
||||
Protos []string
|
||||
Formats []string
|
||||
}
|
||||
|
||||
func (m MediaName) String() string {
|
||||
return stringFromMarshal(m.marshalInto, m.marshalSize)
|
||||
}
|
||||
|
||||
func (m MediaName) marshalInto(b []byte) []byte {
|
||||
appendList := func(list []string, sep byte) {
|
||||
for i, p := range list {
|
||||
if i != 0 && i != len(list) {
|
||||
b = append(b, sep)
|
||||
}
|
||||
b = append(b, p...)
|
||||
}
|
||||
}
|
||||
|
||||
b = append(append(b, m.Media...), ' ')
|
||||
b = append(m.Port.marshalInto(b), ' ')
|
||||
appendList(m.Protos, '/')
|
||||
b = append(b, ' ')
|
||||
appendList(m.Formats, ' ')
|
||||
return b
|
||||
}
|
||||
|
||||
func (m MediaName) marshalSize() (size int) {
|
||||
listSize := func(list []string) {
|
||||
for _, p := range list {
|
||||
size += 1 + len(p)
|
||||
}
|
||||
}
|
||||
|
||||
size = len(m.Media)
|
||||
size += 1 + m.Port.marshalSize()
|
||||
listSize(m.Protos)
|
||||
listSize(m.Formats)
|
||||
|
||||
return size
|
||||
}
|
||||
6
server/vendor/github.com/pion/sdp/v3/renovate.json
generated
vendored
Normal file
6
server/vendor/github.com/pion/sdp/v3/renovate.json
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"github>pion/renovate-config"
|
||||
]
|
||||
}
|
||||
5
server/vendor/github.com/pion/sdp/v3/sdp.go
generated
vendored
Normal file
5
server/vendor/github.com/pion/sdp/v3/sdp.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package sdp implements Session Description Protocol (SDP)
|
||||
package sdp
|
||||
201
server/vendor/github.com/pion/sdp/v3/session_description.go
generated
vendored
Normal file
201
server/vendor/github.com/pion/sdp/v3/session_description.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SessionDescription is a a well-defined format for conveying sufficient
|
||||
// information to discover and participate in a multimedia session.
|
||||
type SessionDescription struct {
|
||||
// v=0
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.1
|
||||
Version Version
|
||||
|
||||
// o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.2
|
||||
Origin Origin
|
||||
|
||||
// s=<session name>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.3
|
||||
SessionName SessionName
|
||||
|
||||
// i=<session description>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.4
|
||||
SessionInformation *Information
|
||||
|
||||
// u=<uri>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.5
|
||||
URI *url.URL
|
||||
|
||||
// e=<email-address>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.6
|
||||
EmailAddress *EmailAddress
|
||||
|
||||
// p=<phone-number>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.6
|
||||
PhoneNumber *PhoneNumber
|
||||
|
||||
// c=<nettype> <addrtype> <connection-address>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.7
|
||||
ConnectionInformation *ConnectionInformation
|
||||
|
||||
// b=<bwtype>:<bandwidth>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.8
|
||||
Bandwidth []Bandwidth
|
||||
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.9
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.10
|
||||
TimeDescriptions []TimeDescription
|
||||
|
||||
// z=<adjustment time> <offset> <adjustment time> <offset> ...
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.11
|
||||
TimeZones []TimeZone
|
||||
|
||||
// k=<method>
|
||||
// k=<method>:<encryption key>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.12
|
||||
EncryptionKey *EncryptionKey
|
||||
|
||||
// a=<attribute>
|
||||
// a=<attribute>:<value>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.13
|
||||
Attributes []Attribute
|
||||
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.14
|
||||
MediaDescriptions []*MediaDescription
|
||||
}
|
||||
|
||||
// Attribute returns the value of an attribute and if it exists
|
||||
func (s *SessionDescription) Attribute(key string) (string, bool) {
|
||||
for _, a := range s.Attributes {
|
||||
if a.Key == key {
|
||||
return a.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Version describes the value provided by the "v=" field which gives
|
||||
// the version of the Session Description Protocol.
|
||||
type Version int
|
||||
|
||||
func (v Version) String() string {
|
||||
return stringFromMarshal(v.marshalInto, v.marshalSize)
|
||||
}
|
||||
|
||||
func (v Version) marshalInto(b []byte) []byte {
|
||||
return strconv.AppendInt(b, int64(v), 10)
|
||||
}
|
||||
|
||||
func (v Version) marshalSize() (size int) {
|
||||
return lenInt(int64(v))
|
||||
}
|
||||
|
||||
// Origin defines the structure for the "o=" field which provides the
|
||||
// originator of the session plus a session identifier and version number.
|
||||
type Origin struct {
|
||||
Username string
|
||||
SessionID uint64
|
||||
SessionVersion uint64
|
||||
NetworkType string
|
||||
AddressType string
|
||||
UnicastAddress string
|
||||
}
|
||||
|
||||
func (o Origin) String() string {
|
||||
return stringFromMarshal(o.marshalInto, o.marshalSize)
|
||||
}
|
||||
|
||||
func (o Origin) marshalInto(b []byte) []byte {
|
||||
b = append(append(b, o.Username...), ' ')
|
||||
b = append(strconv.AppendUint(b, o.SessionID, 10), ' ')
|
||||
b = append(strconv.AppendUint(b, o.SessionVersion, 10), ' ')
|
||||
b = append(append(b, o.NetworkType...), ' ')
|
||||
b = append(append(b, o.AddressType...), ' ')
|
||||
return append(b, o.UnicastAddress...)
|
||||
}
|
||||
|
||||
func (o Origin) marshalSize() (size int) {
|
||||
return len(o.Username) +
|
||||
lenUint(o.SessionID) +
|
||||
lenUint(o.SessionVersion) +
|
||||
len(o.NetworkType) +
|
||||
len(o.AddressType) +
|
||||
len(o.UnicastAddress) +
|
||||
5
|
||||
}
|
||||
|
||||
// SessionName describes a structured representations for the "s=" field
|
||||
// and is the textual session name.
|
||||
type SessionName string
|
||||
|
||||
func (s SessionName) String() string {
|
||||
return stringFromMarshal(s.marshalInto, s.marshalSize)
|
||||
}
|
||||
|
||||
func (s SessionName) marshalInto(b []byte) []byte {
|
||||
return append(b, s...)
|
||||
}
|
||||
|
||||
func (s SessionName) marshalSize() (size int) {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// EmailAddress describes a structured representations for the "e=" line
|
||||
// which specifies email contact information for the person responsible for
|
||||
// the conference.
|
||||
type EmailAddress string
|
||||
|
||||
func (e EmailAddress) String() string {
|
||||
return stringFromMarshal(e.marshalInto, e.marshalSize)
|
||||
}
|
||||
|
||||
func (e EmailAddress) marshalInto(b []byte) []byte {
|
||||
return append(b, e...)
|
||||
}
|
||||
|
||||
func (e EmailAddress) marshalSize() (size int) {
|
||||
return len(e)
|
||||
}
|
||||
|
||||
// PhoneNumber describes a structured representations for the "p=" line
|
||||
// specify phone contact information for the person responsible for the
|
||||
// conference.
|
||||
type PhoneNumber string
|
||||
|
||||
func (p PhoneNumber) String() string {
|
||||
return stringFromMarshal(p.marshalInto, p.marshalSize)
|
||||
}
|
||||
|
||||
func (p PhoneNumber) marshalInto(b []byte) []byte {
|
||||
return append(b, p...)
|
||||
}
|
||||
|
||||
func (p PhoneNumber) marshalSize() (size int) {
|
||||
return len(p)
|
||||
}
|
||||
|
||||
// TimeZone defines the structured object for "z=" line which describes
|
||||
// repeated sessions scheduling.
|
||||
type TimeZone struct {
|
||||
AdjustmentTime uint64
|
||||
Offset int64
|
||||
}
|
||||
|
||||
func (z TimeZone) String() string {
|
||||
return stringFromMarshal(z.marshalInto, z.marshalSize)
|
||||
}
|
||||
|
||||
func (z TimeZone) marshalInto(b []byte) []byte {
|
||||
b = strconv.AppendUint(b, z.AdjustmentTime, 10)
|
||||
b = append(b, ' ')
|
||||
return strconv.AppendInt(b, z.Offset, 10)
|
||||
}
|
||||
|
||||
func (z TimeZone) marshalSize() (size int) {
|
||||
return lenUint(z.AdjustmentTime) + 1 + lenInt(z.Offset)
|
||||
}
|
||||
75
server/vendor/github.com/pion/sdp/v3/time_description.go
generated
vendored
Normal file
75
server/vendor/github.com/pion/sdp/v3/time_description.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// TimeDescription describes "t=", "r=" fields of the session description
|
||||
// which are used to specify the start and stop times for a session as well as
|
||||
// repeat intervals and durations for the scheduled session.
|
||||
type TimeDescription struct {
|
||||
// t=<start-time> <stop-time>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.9
|
||||
Timing Timing
|
||||
|
||||
// r=<repeat interval> <active duration> <offsets from start-time>
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.10
|
||||
RepeatTimes []RepeatTime
|
||||
}
|
||||
|
||||
// Timing defines the "t=" field's structured representation for the start and
|
||||
// stop times.
|
||||
type Timing struct {
|
||||
StartTime uint64
|
||||
StopTime uint64
|
||||
}
|
||||
|
||||
func (t Timing) String() string {
|
||||
return stringFromMarshal(t.marshalInto, t.marshalSize)
|
||||
}
|
||||
|
||||
func (t Timing) marshalInto(b []byte) []byte {
|
||||
b = append(strconv.AppendUint(b, t.StartTime, 10), ' ')
|
||||
return strconv.AppendUint(b, t.StopTime, 10)
|
||||
}
|
||||
|
||||
func (t Timing) marshalSize() (size int) {
|
||||
return lenUint(t.StartTime) + 1 + lenUint(t.StopTime)
|
||||
}
|
||||
|
||||
// RepeatTime describes the "r=" fields of the session description which
|
||||
// represents the intervals and durations for repeated scheduled sessions.
|
||||
type RepeatTime struct {
|
||||
Interval int64
|
||||
Duration int64
|
||||
Offsets []int64
|
||||
}
|
||||
|
||||
func (r RepeatTime) String() string {
|
||||
return stringFromMarshal(r.marshalInto, r.marshalSize)
|
||||
}
|
||||
|
||||
func (r RepeatTime) marshalInto(b []byte) []byte {
|
||||
b = strconv.AppendInt(b, r.Interval, 10)
|
||||
b = append(b, ' ')
|
||||
b = strconv.AppendInt(b, r.Duration, 10)
|
||||
for _, value := range r.Offsets {
|
||||
b = append(b, ' ')
|
||||
b = strconv.AppendInt(b, value, 10)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (r RepeatTime) marshalSize() (size int) {
|
||||
size = lenInt(r.Interval)
|
||||
size += 1 + lenInt(r.Duration)
|
||||
for _, o := range r.Offsets {
|
||||
size += 1 + lenInt(o)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
956
server/vendor/github.com/pion/sdp/v3/unmarshal.go
generated
vendored
Normal file
956
server/vendor/github.com/pion/sdp/v3/unmarshal.go
generated
vendored
Normal file
@@ -0,0 +1,956 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
errSDPInvalidSyntax = errors.New("sdp: invalid syntax")
|
||||
errSDPInvalidNumericValue = errors.New("sdp: invalid numeric value")
|
||||
errSDPInvalidValue = errors.New("sdp: invalid value")
|
||||
errSDPInvalidPortValue = errors.New("sdp: invalid port value")
|
||||
errSDPCacheInvalid = errors.New("sdp: invalid cache")
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
unmarshalCachePool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &unmarshalCache{}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// UnmarshalString is the primary function that deserializes the session description
|
||||
// message and stores it inside of a structured SessionDescription object.
|
||||
//
|
||||
// The States Transition Table describes the computation flow between functions
|
||||
// (namely s1, s2, s3, ...) for a parsing procedure that complies with the
|
||||
// specifications laid out by the rfc4566#section-5 as well as by JavaScript
|
||||
// Session Establishment Protocol draft. Links:
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc4566#section-5
|
||||
// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc4566#section-5
|
||||
// Session description
|
||||
//
|
||||
// v= (protocol version)
|
||||
// o= (originator and session identifier)
|
||||
// s= (session name)
|
||||
// i=* (session information)
|
||||
// u=* (URI of description)
|
||||
// e=* (email address)
|
||||
// p=* (phone number)
|
||||
// c=* (connection information -- not required if included in
|
||||
// all media)
|
||||
// b=* (zero or more bandwidth information lines)
|
||||
// One or more time descriptions ("t=" and "r=" lines; see below)
|
||||
// z=* (time zone adjustments)
|
||||
// k=* (encryption key)
|
||||
// a=* (zero or more session attribute lines)
|
||||
// Zero or more media descriptions
|
||||
//
|
||||
// Time description
|
||||
//
|
||||
// t= (time the session is active)
|
||||
// r=* (zero or more repeat times)
|
||||
//
|
||||
// Media description, if present
|
||||
//
|
||||
// m= (media name and transport address)
|
||||
// i=* (media title)
|
||||
// c=* (connection information -- optional if included at
|
||||
// session level)
|
||||
// b=* (zero or more bandwidth information lines)
|
||||
// k=* (encryption key)
|
||||
// a=* (zero or more media attribute lines)
|
||||
//
|
||||
// In order to generate the following state table and draw subsequent
|
||||
// deterministic finite-state automota ("DFA") the following regex was used to
|
||||
// derive the DFA:
|
||||
//
|
||||
// vosi?u?e?p?c?b*(tr*)+z?k?a*(mi?c?b*k?a*)*
|
||||
//
|
||||
// possible place and state to exit:
|
||||
//
|
||||
// ** * * * ** * * * *
|
||||
// 99 1 1 1 11 1 1 1 1
|
||||
// 3 1 1 26 5 5 4 4
|
||||
//
|
||||
// Please pay close attention to the `k`, and `a` parsing states. In the table
|
||||
// below in order to distinguish between the states belonging to the media
|
||||
// description as opposed to the session description, the states are marked
|
||||
// with an asterisk ("a*", "k*").
|
||||
// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+
|
||||
// | STATES | a* | a*,k* | a | a,k | b | b,c | e | i | m | o | p | r,t | s | t | u | v | z |
|
||||
// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+
|
||||
// | s1 | | | | | | | | | | | | | | | | 2 | |
|
||||
// | s2 | | | | | | | | | | 3 | | | | | | | |
|
||||
// | s3 | | | | | | | | | | | | | 4 | | | | |
|
||||
// | s4 | | | | | | 5 | 6 | 7 | | | 8 | | | 9 | 10 | | |
|
||||
// | s5 | | | | | 5 | | | | | | | | | 9 | | | |
|
||||
// | s6 | | | | | | 5 | | | | | 8 | | | 9 | | | |
|
||||
// | s7 | | | | | | 5 | 6 | | | | 8 | | | 9 | 10 | | |
|
||||
// | s8 | | | | | | 5 | | | | | | | | 9 | | | |
|
||||
// | s9 | | | | 11 | | | | | 12 | | | 9 | | | | | 13 |
|
||||
// | s10 | | | | | | 5 | 6 | | | | 8 | | | 9 | | | |
|
||||
// | s11 | | | 11 | | | | | | 12 | | | | | | | | |
|
||||
// | s12 | | 14 | | | | 15 | | 16 | 12 | | | | | | | | |
|
||||
// | s13 | | | | 11 | | | | | 12 | | | | | | | | |
|
||||
// | s14 | 14 | | | | | | | | 12 | | | | | | | | |
|
||||
// | s15 | | 14 | | | 15 | | | | 12 | | | | | | | | |
|
||||
// | s16 | | 14 | | | | 15 | | | 12 | | | | | | | | |
|
||||
// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+
|
||||
func (s *SessionDescription) UnmarshalString(value string) error {
|
||||
var ok bool
|
||||
l := new(lexer)
|
||||
if l.cache, ok = unmarshalCachePool.Get().(*unmarshalCache); !ok {
|
||||
return errSDPCacheInvalid
|
||||
}
|
||||
defer unmarshalCachePool.Put(l.cache)
|
||||
|
||||
l.cache.reset()
|
||||
l.desc = s
|
||||
l.value = value
|
||||
|
||||
for state := s1; state != nil; {
|
||||
var err error
|
||||
state, err = state(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.Attributes = l.cache.cloneSessionAttributes()
|
||||
populateMediaAttributes(l.cache, l.desc)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshal converts the value into a []byte and then calls UnmarshalString.
|
||||
// Callers should use the more performant UnmarshalString
|
||||
func (s *SessionDescription) Unmarshal(value []byte) error {
|
||||
return s.UnmarshalString(string(value))
|
||||
}
|
||||
|
||||
func s1(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
if key == 'v' {
|
||||
return unmarshalProtocolVersion
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s2(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
if key == 'o' {
|
||||
return unmarshalOrigin
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s3(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
if key == 's' {
|
||||
return unmarshalSessionName
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s4(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'i':
|
||||
return unmarshalSessionInformation
|
||||
case 'u':
|
||||
return unmarshalURI
|
||||
case 'e':
|
||||
return unmarshalEmail
|
||||
case 'p':
|
||||
return unmarshalPhone
|
||||
case 'c':
|
||||
return unmarshalSessionConnectionInformation
|
||||
case 'b':
|
||||
return unmarshalSessionBandwidth
|
||||
case 't':
|
||||
return unmarshalTiming
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s5(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'b':
|
||||
return unmarshalSessionBandwidth
|
||||
case 't':
|
||||
return unmarshalTiming
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s6(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'p':
|
||||
return unmarshalPhone
|
||||
case 'c':
|
||||
return unmarshalSessionConnectionInformation
|
||||
case 'b':
|
||||
return unmarshalSessionBandwidth
|
||||
case 't':
|
||||
return unmarshalTiming
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s7(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'u':
|
||||
return unmarshalURI
|
||||
case 'e':
|
||||
return unmarshalEmail
|
||||
case 'p':
|
||||
return unmarshalPhone
|
||||
case 'c':
|
||||
return unmarshalSessionConnectionInformation
|
||||
case 'b':
|
||||
return unmarshalSessionBandwidth
|
||||
case 't':
|
||||
return unmarshalTiming
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s8(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'c':
|
||||
return unmarshalSessionConnectionInformation
|
||||
case 'b':
|
||||
return unmarshalSessionBandwidth
|
||||
case 't':
|
||||
return unmarshalTiming
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s9(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'z':
|
||||
return unmarshalTimeZones
|
||||
case 'k':
|
||||
return unmarshalSessionEncryptionKey
|
||||
case 'a':
|
||||
return unmarshalSessionAttribute
|
||||
case 'r':
|
||||
return unmarshalRepeatTimes
|
||||
case 't':
|
||||
return unmarshalTiming
|
||||
case 'm':
|
||||
return unmarshalMediaDescription
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s10(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'e':
|
||||
return unmarshalEmail
|
||||
case 'p':
|
||||
return unmarshalPhone
|
||||
case 'c':
|
||||
return unmarshalSessionConnectionInformation
|
||||
case 'b':
|
||||
return unmarshalSessionBandwidth
|
||||
case 't':
|
||||
return unmarshalTiming
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s11(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'a':
|
||||
return unmarshalSessionAttribute
|
||||
case 'm':
|
||||
return unmarshalMediaDescription
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s12(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'a':
|
||||
return unmarshalMediaAttribute
|
||||
case 'k':
|
||||
return unmarshalMediaEncryptionKey
|
||||
case 'b':
|
||||
return unmarshalMediaBandwidth
|
||||
case 'c':
|
||||
return unmarshalMediaConnectionInformation
|
||||
case 'i':
|
||||
return unmarshalMediaTitle
|
||||
case 'm':
|
||||
return unmarshalMediaDescription
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s13(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'a':
|
||||
return unmarshalSessionAttribute
|
||||
case 'k':
|
||||
return unmarshalSessionEncryptionKey
|
||||
case 'm':
|
||||
return unmarshalMediaDescription
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s14(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'a':
|
||||
return unmarshalMediaAttribute
|
||||
case 'k':
|
||||
// Non-spec ordering
|
||||
return unmarshalMediaEncryptionKey
|
||||
case 'b':
|
||||
// Non-spec ordering
|
||||
return unmarshalMediaBandwidth
|
||||
case 'c':
|
||||
// Non-spec ordering
|
||||
return unmarshalMediaConnectionInformation
|
||||
case 'i':
|
||||
// Non-spec ordering
|
||||
return unmarshalMediaTitle
|
||||
case 'm':
|
||||
return unmarshalMediaDescription
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s15(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'a':
|
||||
return unmarshalMediaAttribute
|
||||
case 'k':
|
||||
return unmarshalMediaEncryptionKey
|
||||
case 'b':
|
||||
return unmarshalMediaBandwidth
|
||||
case 'c':
|
||||
return unmarshalMediaConnectionInformation
|
||||
case 'i':
|
||||
// Non-spec ordering
|
||||
return unmarshalMediaTitle
|
||||
case 'm':
|
||||
return unmarshalMediaDescription
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func s16(l *lexer) (stateFn, error) {
|
||||
return l.handleType(func(key byte) stateFn {
|
||||
switch key {
|
||||
case 'a':
|
||||
return unmarshalMediaAttribute
|
||||
case 'k':
|
||||
return unmarshalMediaEncryptionKey
|
||||
case 'c':
|
||||
return unmarshalMediaConnectionInformation
|
||||
case 'b':
|
||||
return unmarshalMediaBandwidth
|
||||
case 'i':
|
||||
// Non-spec ordering
|
||||
return unmarshalMediaTitle
|
||||
case 'm':
|
||||
return unmarshalMediaDescription
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func unmarshalProtocolVersion(l *lexer) (stateFn, error) {
|
||||
version, err := l.readUint64Field()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// As off the latest draft of the rfc this value is required to be 0.
|
||||
// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24#section-5.8.1
|
||||
if version != 0 {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, version)
|
||||
}
|
||||
|
||||
if err := l.nextLine(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s2, nil
|
||||
}
|
||||
|
||||
func unmarshalOrigin(l *lexer) (stateFn, error) {
|
||||
var err error
|
||||
|
||||
l.desc.Origin.Username, err = l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.desc.Origin.SessionID, err = l.readUint64Field()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.desc.Origin.SessionVersion, err = l.readUint64Field()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.desc.Origin.NetworkType, err = l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set according to currently registered with IANA
|
||||
// https://tools.ietf.org/html/rfc4566#section-8.2.6
|
||||
if !anyOf(l.desc.Origin.NetworkType, "IN") {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, l.desc.Origin.NetworkType)
|
||||
}
|
||||
|
||||
l.desc.Origin.AddressType, err = l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set according to currently registered with IANA
|
||||
// https://tools.ietf.org/html/rfc4566#section-8.2.7
|
||||
if !anyOf(l.desc.Origin.AddressType, "IP4", "IP6") {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, l.desc.Origin.AddressType)
|
||||
}
|
||||
|
||||
l.desc.Origin.UnicastAddress, err = l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := l.nextLine(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s3, nil
|
||||
}
|
||||
|
||||
func unmarshalSessionName(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.desc.SessionName = SessionName(value)
|
||||
return s4, nil
|
||||
}
|
||||
|
||||
func unmarshalSessionInformation(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sessionInformation := Information(value)
|
||||
l.desc.SessionInformation = &sessionInformation
|
||||
return s7, nil
|
||||
}
|
||||
|
||||
func unmarshalURI(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.desc.URI, err = url.Parse(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s10, nil
|
||||
}
|
||||
|
||||
func unmarshalEmail(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
emailAddress := EmailAddress(value)
|
||||
l.desc.EmailAddress = &emailAddress
|
||||
return s6, nil
|
||||
}
|
||||
|
||||
func unmarshalPhone(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
phoneNumber := PhoneNumber(value)
|
||||
l.desc.PhoneNumber = &phoneNumber
|
||||
return s8, nil
|
||||
}
|
||||
|
||||
func unmarshalSessionConnectionInformation(l *lexer) (stateFn, error) {
|
||||
var err error
|
||||
l.desc.ConnectionInformation, err = l.unmarshalConnectionInformation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s5, nil
|
||||
}
|
||||
|
||||
func (l *lexer) unmarshalConnectionInformation() (*ConnectionInformation, error) {
|
||||
var err error
|
||||
var c ConnectionInformation
|
||||
|
||||
c.NetworkType, err = l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set according to currently registered with IANA
|
||||
// https://tools.ietf.org/html/rfc4566#section-8.2.6
|
||||
if !anyOf(c.NetworkType, "IN") {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, c.NetworkType)
|
||||
}
|
||||
|
||||
c.AddressType, err = l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set according to currently registered with IANA
|
||||
// https://tools.ietf.org/html/rfc4566#section-8.2.7
|
||||
if !anyOf(c.AddressType, "IP4", "IP6") {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, c.AddressType)
|
||||
}
|
||||
|
||||
address, err := l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if address != "" {
|
||||
c.Address = new(Address)
|
||||
c.Address.Address = address
|
||||
}
|
||||
|
||||
if err := l.nextLine(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func unmarshalSessionBandwidth(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bandwidth, err := unmarshalBandwidth(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w `b=%v`", errSDPInvalidValue, value)
|
||||
}
|
||||
l.desc.Bandwidth = append(l.desc.Bandwidth, *bandwidth)
|
||||
|
||||
return s5, nil
|
||||
}
|
||||
|
||||
func unmarshalBandwidth(value string) (*Bandwidth, error) {
|
||||
parts := strings.Split(value, ":")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("%w `b=%v`", errSDPInvalidValue, parts)
|
||||
}
|
||||
|
||||
experimental := strings.HasPrefix(parts[0], "X-")
|
||||
if experimental {
|
||||
parts[0] = strings.TrimPrefix(parts[0], "X-")
|
||||
} else if !anyOf(parts[0], "CT", "AS", "TIAS", "RS", "RR") {
|
||||
// Set according to currently registered with IANA
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.8
|
||||
// https://tools.ietf.org/html/rfc3890#section-6.2
|
||||
// https://tools.ietf.org/html/rfc3556#section-2
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, parts[0])
|
||||
}
|
||||
|
||||
bandwidth, err := strconv.ParseUint(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, parts[1])
|
||||
}
|
||||
|
||||
return &Bandwidth{
|
||||
Experimental: experimental,
|
||||
Type: parts[0],
|
||||
Bandwidth: bandwidth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func unmarshalTiming(l *lexer) (stateFn, error) {
|
||||
var err error
|
||||
var td TimeDescription
|
||||
|
||||
td.Timing.StartTime, err = l.readUint64Field()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
td.Timing.StopTime, err = l.readUint64Field()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := l.nextLine(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.desc.TimeDescriptions = append(l.desc.TimeDescriptions, td)
|
||||
return s9, nil
|
||||
}
|
||||
|
||||
func unmarshalRepeatTimes(l *lexer) (stateFn, error) {
|
||||
var err error
|
||||
var newRepeatTime RepeatTime
|
||||
|
||||
latestTimeDesc := &l.desc.TimeDescriptions[len(l.desc.TimeDescriptions)-1]
|
||||
|
||||
field, err := l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newRepeatTime.Interval, err = parseTimeUnits(field)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field)
|
||||
}
|
||||
|
||||
field, err = l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newRepeatTime.Duration, err = parseTimeUnits(field)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field)
|
||||
}
|
||||
|
||||
for {
|
||||
field, err := l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if field == "" {
|
||||
break
|
||||
}
|
||||
offset, err := parseTimeUnits(field)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field)
|
||||
}
|
||||
newRepeatTime.Offsets = append(newRepeatTime.Offsets, offset)
|
||||
}
|
||||
|
||||
if err := l.nextLine(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latestTimeDesc.RepeatTimes = append(latestTimeDesc.RepeatTimes, newRepeatTime)
|
||||
return s9, nil
|
||||
}
|
||||
|
||||
func unmarshalTimeZones(l *lexer) (stateFn, error) {
|
||||
// These fields are transimitted in pairs
|
||||
// z=<adjustment time> <offset> <adjustment time> <offset> ....
|
||||
// so we are making sure that there are actually multiple of 2 total.
|
||||
for {
|
||||
var err error
|
||||
var timeZone TimeZone
|
||||
|
||||
timeZone.AdjustmentTime, err = l.readUint64Field()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offset, err := l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if offset == "" {
|
||||
break
|
||||
}
|
||||
|
||||
timeZone.Offset, err = parseTimeUnits(offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.desc.TimeZones = append(l.desc.TimeZones, timeZone)
|
||||
}
|
||||
|
||||
if err := l.nextLine(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s13, nil
|
||||
}
|
||||
|
||||
func unmarshalSessionEncryptionKey(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encryptionKey := EncryptionKey(value)
|
||||
l.desc.EncryptionKey = &encryptionKey
|
||||
return s11, nil
|
||||
}
|
||||
|
||||
func unmarshalSessionAttribute(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i := strings.IndexRune(value, ':')
|
||||
a := l.cache.getSessionAttribute()
|
||||
if i > 0 {
|
||||
a.Key = value[:i]
|
||||
a.Value = value[i+1:]
|
||||
} else {
|
||||
a.Key = value
|
||||
}
|
||||
|
||||
return s11, nil
|
||||
}
|
||||
|
||||
func unmarshalMediaDescription(l *lexer) (stateFn, error) {
|
||||
populateMediaAttributes(l.cache, l.desc)
|
||||
var newMediaDesc MediaDescription
|
||||
|
||||
// <media>
|
||||
field, err := l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set according to currently registered with IANA
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.14
|
||||
if !anyOf(field, "audio", "video", "text", "application", "message") {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field)
|
||||
}
|
||||
newMediaDesc.MediaName.Media = field
|
||||
|
||||
// <port>
|
||||
field, err = l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parts := strings.Split(field, "/")
|
||||
newMediaDesc.MediaName.Port.Value, err = parsePort(parts[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidPortValue, parts[0])
|
||||
}
|
||||
|
||||
if len(parts) > 1 {
|
||||
var portRange int
|
||||
portRange, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, parts)
|
||||
}
|
||||
newMediaDesc.MediaName.Port.Range = &portRange
|
||||
}
|
||||
|
||||
// <proto>
|
||||
field, err = l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set according to currently registered with IANA
|
||||
// https://tools.ietf.org/html/rfc4566#section-5.14
|
||||
// https://tools.ietf.org/html/rfc4975#section-8.1
|
||||
for _, proto := range strings.Split(field, "/") {
|
||||
if !anyOf(proto, "UDP", "RTP", "AVP", "SAVP", "SAVPF", "TLS", "DTLS", "SCTP", "AVPF", "TCP", "MSRP", "BFCP", "UDT", "IX") {
|
||||
return nil, fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, field)
|
||||
}
|
||||
newMediaDesc.MediaName.Protos = append(newMediaDesc.MediaName.Protos, proto)
|
||||
}
|
||||
|
||||
// <fmt>...
|
||||
for {
|
||||
field, err = l.readField()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if field == "" {
|
||||
break
|
||||
}
|
||||
newMediaDesc.MediaName.Formats = append(newMediaDesc.MediaName.Formats, field)
|
||||
}
|
||||
|
||||
if err := l.nextLine(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.desc.MediaDescriptions = append(l.desc.MediaDescriptions, &newMediaDesc)
|
||||
return s12, nil
|
||||
}
|
||||
|
||||
func unmarshalMediaTitle(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
|
||||
mediaTitle := Information(value)
|
||||
latestMediaDesc.MediaTitle = &mediaTitle
|
||||
return s16, nil
|
||||
}
|
||||
|
||||
func unmarshalMediaConnectionInformation(l *lexer) (stateFn, error) {
|
||||
var err error
|
||||
latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
|
||||
latestMediaDesc.ConnectionInformation, err = l.unmarshalConnectionInformation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s15, nil
|
||||
}
|
||||
|
||||
func unmarshalMediaBandwidth(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
|
||||
bandwidth, err := unmarshalBandwidth(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w `b=%v`", errSDPInvalidSyntax, value)
|
||||
}
|
||||
latestMediaDesc.Bandwidth = append(latestMediaDesc.Bandwidth, *bandwidth)
|
||||
return s15, nil
|
||||
}
|
||||
|
||||
func unmarshalMediaEncryptionKey(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1]
|
||||
encryptionKey := EncryptionKey(value)
|
||||
latestMediaDesc.EncryptionKey = &encryptionKey
|
||||
return s14, nil
|
||||
}
|
||||
|
||||
func unmarshalMediaAttribute(l *lexer) (stateFn, error) {
|
||||
value, err := l.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i := strings.IndexRune(value, ':')
|
||||
a := l.cache.getMediaAttribute()
|
||||
if i > 0 {
|
||||
a.Key = value[:i]
|
||||
a.Value = value[i+1:]
|
||||
} else {
|
||||
a.Key = value
|
||||
}
|
||||
|
||||
return s14, nil
|
||||
}
|
||||
|
||||
func parseTimeUnits(value string) (num int64, err error) {
|
||||
if len(value) == 0 {
|
||||
return 0, fmt.Errorf("%w `%v`", errSDPInvalidValue, value)
|
||||
}
|
||||
k := timeShorthand(value[len(value)-1])
|
||||
if k > 0 {
|
||||
num, err = strconv.ParseInt(value[:len(value)-1], 10, 64)
|
||||
} else {
|
||||
k = 1
|
||||
num, err = strconv.ParseInt(value, 10, 64)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w `%v`", errSDPInvalidValue, value)
|
||||
}
|
||||
return num * k, nil
|
||||
}
|
||||
|
||||
func timeShorthand(b byte) int64 {
|
||||
// Some time offsets in the protocol can be provided with a shorthand
|
||||
// notation. This code ensures to convert it to NTP timestamp format.
|
||||
switch b {
|
||||
case 'd': // days
|
||||
return 86400
|
||||
case 'h': // hours
|
||||
return 3600
|
||||
case 'm': // minutes
|
||||
return 60
|
||||
case 's': // seconds (allowed for completeness)
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func parsePort(value string) (int, error) {
|
||||
port, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w `%v`", errSDPInvalidPortValue, port)
|
||||
}
|
||||
|
||||
if port < 0 || port > 65536 {
|
||||
return 0, fmt.Errorf("%w -- out of range `%v`", errSDPInvalidPortValue, port)
|
||||
}
|
||||
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func populateMediaAttributes(c *unmarshalCache, s *SessionDescription) {
|
||||
if len(s.MediaDescriptions) != 0 {
|
||||
lastMediaDesc := s.MediaDescriptions[len(s.MediaDescriptions)-1]
|
||||
lastMediaDesc.Attributes = c.cloneMediaAttributes()
|
||||
}
|
||||
}
|
||||
44
server/vendor/github.com/pion/sdp/v3/unmarshal_cache.go
generated
vendored
Normal file
44
server/vendor/github.com/pion/sdp/v3/unmarshal_cache.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
type unmarshalCache struct {
|
||||
sessionAttributes []Attribute
|
||||
mediaAttributes []Attribute
|
||||
}
|
||||
|
||||
func (c *unmarshalCache) reset() {
|
||||
c.sessionAttributes = c.sessionAttributes[:0]
|
||||
c.mediaAttributes = c.mediaAttributes[:0]
|
||||
}
|
||||
|
||||
func (c *unmarshalCache) getSessionAttribute() *Attribute {
|
||||
c.sessionAttributes = append(c.sessionAttributes, Attribute{})
|
||||
return &c.sessionAttributes[len(c.sessionAttributes)-1]
|
||||
}
|
||||
|
||||
func (c *unmarshalCache) cloneSessionAttributes() []Attribute {
|
||||
if len(c.sessionAttributes) == 0 {
|
||||
return nil
|
||||
}
|
||||
s := make([]Attribute, len(c.sessionAttributes))
|
||||
copy(s, c.sessionAttributes)
|
||||
c.sessionAttributes = c.sessionAttributes[:0]
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *unmarshalCache) getMediaAttribute() *Attribute {
|
||||
c.mediaAttributes = append(c.mediaAttributes, Attribute{})
|
||||
return &c.mediaAttributes[len(c.mediaAttributes)-1]
|
||||
}
|
||||
|
||||
func (c *unmarshalCache) cloneMediaAttributes() []Attribute {
|
||||
if len(c.mediaAttributes) == 0 {
|
||||
return nil
|
||||
}
|
||||
s := make([]Attribute, len(c.mediaAttributes))
|
||||
copy(s, c.mediaAttributes)
|
||||
c.mediaAttributes = c.mediaAttributes[:0]
|
||||
return s
|
||||
}
|
||||
360
server/vendor/github.com/pion/sdp/v3/util.go
generated
vendored
Normal file
360
server/vendor/github.com/pion/sdp/v3/util.go
generated
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package sdp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pion/randutil"
|
||||
)
|
||||
|
||||
const (
|
||||
attributeKey = "a="
|
||||
)
|
||||
|
||||
var (
|
||||
errExtractCodecRtpmap = errors.New("could not extract codec from rtpmap")
|
||||
errExtractCodecFmtp = errors.New("could not extract codec from fmtp")
|
||||
errExtractCodecRtcpFb = errors.New("could not extract codec from rtcp-fb")
|
||||
errPayloadTypeNotFound = errors.New("payload type not found")
|
||||
errCodecNotFound = errors.New("codec not found")
|
||||
errSyntaxError = errors.New("SyntaxError")
|
||||
)
|
||||
|
||||
// ConnectionRole indicates which of the end points should initiate the connection establishment
|
||||
type ConnectionRole int
|
||||
|
||||
const (
|
||||
// ConnectionRoleActive indicates the endpoint will initiate an outgoing connection.
|
||||
ConnectionRoleActive ConnectionRole = iota + 1
|
||||
|
||||
// ConnectionRolePassive indicates the endpoint will accept an incoming connection.
|
||||
ConnectionRolePassive
|
||||
|
||||
// ConnectionRoleActpass indicates the endpoint is willing to accept an incoming connection or to initiate an outgoing connection.
|
||||
ConnectionRoleActpass
|
||||
|
||||
// ConnectionRoleHoldconn indicates the endpoint does not want the connection to be established for the time being.
|
||||
ConnectionRoleHoldconn
|
||||
)
|
||||
|
||||
func (t ConnectionRole) String() string {
|
||||
switch t {
|
||||
case ConnectionRoleActive:
|
||||
return "active"
|
||||
case ConnectionRolePassive:
|
||||
return "passive"
|
||||
case ConnectionRoleActpass:
|
||||
return "actpass"
|
||||
case ConnectionRoleHoldconn:
|
||||
return "holdconn"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func newSessionID() (uint64, error) {
|
||||
// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26#section-5.2.1
|
||||
// Session ID is recommended to be constructed by generating a 64-bit
|
||||
// quantity with the highest bit set to zero and the remaining 63-bits
|
||||
// being cryptographically random.
|
||||
id, err := randutil.CryptoUint64()
|
||||
return id & (^(uint64(1) << 63)), err
|
||||
}
|
||||
|
||||
// Codec represents a codec
|
||||
type Codec struct {
|
||||
PayloadType uint8
|
||||
Name string
|
||||
ClockRate uint32
|
||||
EncodingParameters string
|
||||
Fmtp string
|
||||
RTCPFeedback []string
|
||||
}
|
||||
|
||||
const (
|
||||
unknown = iota
|
||||
)
|
||||
|
||||
func (c Codec) String() string {
|
||||
return fmt.Sprintf("%d %s/%d/%s (%s) [%s]", c.PayloadType, c.Name, c.ClockRate, c.EncodingParameters, c.Fmtp, strings.Join(c.RTCPFeedback, ", "))
|
||||
}
|
||||
|
||||
func (c *Codec) appendRTCPFeedback(rtcpFeedback string) {
|
||||
for _, existingRTCPFeedback := range c.RTCPFeedback {
|
||||
if existingRTCPFeedback == rtcpFeedback {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.RTCPFeedback = append(c.RTCPFeedback, rtcpFeedback)
|
||||
}
|
||||
|
||||
func parseRtpmap(rtpmap string) (Codec, error) {
|
||||
var codec Codec
|
||||
parsingFailed := errExtractCodecRtpmap
|
||||
|
||||
// a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters>]
|
||||
split := strings.Split(rtpmap, " ")
|
||||
if len(split) != 2 {
|
||||
return codec, parsingFailed
|
||||
}
|
||||
|
||||
ptSplit := strings.Split(split[0], ":")
|
||||
if len(ptSplit) != 2 {
|
||||
return codec, parsingFailed
|
||||
}
|
||||
|
||||
ptInt, err := strconv.ParseUint(ptSplit[1], 10, 8)
|
||||
if err != nil {
|
||||
return codec, parsingFailed
|
||||
}
|
||||
|
||||
codec.PayloadType = uint8(ptInt)
|
||||
|
||||
split = strings.Split(split[1], "/")
|
||||
codec.Name = split[0]
|
||||
parts := len(split)
|
||||
if parts > 1 {
|
||||
rate, err := strconv.ParseUint(split[1], 10, 32)
|
||||
if err != nil {
|
||||
return codec, parsingFailed
|
||||
}
|
||||
codec.ClockRate = uint32(rate)
|
||||
}
|
||||
if parts > 2 {
|
||||
codec.EncodingParameters = split[2]
|
||||
}
|
||||
|
||||
return codec, nil
|
||||
}
|
||||
|
||||
func parseFmtp(fmtp string) (Codec, error) {
|
||||
var codec Codec
|
||||
parsingFailed := errExtractCodecFmtp
|
||||
|
||||
// a=fmtp:<format> <format specific parameters>
|
||||
split := strings.Split(fmtp, " ")
|
||||
if len(split) != 2 {
|
||||
return codec, parsingFailed
|
||||
}
|
||||
|
||||
formatParams := split[1]
|
||||
|
||||
split = strings.Split(split[0], ":")
|
||||
if len(split) != 2 {
|
||||
return codec, parsingFailed
|
||||
}
|
||||
|
||||
ptInt, err := strconv.ParseUint(split[1], 10, 8)
|
||||
if err != nil {
|
||||
return codec, parsingFailed
|
||||
}
|
||||
|
||||
codec.PayloadType = uint8(ptInt)
|
||||
codec.Fmtp = formatParams
|
||||
|
||||
return codec, nil
|
||||
}
|
||||
|
||||
func parseRtcpFb(rtcpFb string) (codec Codec, isWildcard bool, err error) {
|
||||
var ptInt uint64
|
||||
err = errExtractCodecRtcpFb
|
||||
|
||||
// a=ftcp-fb:<payload type> <RTCP feedback type> [<RTCP feedback parameter>]
|
||||
split := strings.SplitN(rtcpFb, " ", 2)
|
||||
if len(split) != 2 {
|
||||
return
|
||||
}
|
||||
|
||||
ptSplit := strings.Split(split[0], ":")
|
||||
if len(ptSplit) != 2 {
|
||||
return
|
||||
}
|
||||
|
||||
isWildcard = ptSplit[1] == "*"
|
||||
if !isWildcard {
|
||||
ptInt, err = strconv.ParseUint(ptSplit[1], 10, 8)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
codec.PayloadType = uint8(ptInt)
|
||||
}
|
||||
|
||||
codec.RTCPFeedback = append(codec.RTCPFeedback, split[1])
|
||||
return codec, isWildcard, nil
|
||||
}
|
||||
|
||||
func mergeCodecs(codec Codec, codecs map[uint8]Codec) {
|
||||
savedCodec := codecs[codec.PayloadType]
|
||||
|
||||
if savedCodec.PayloadType == 0 {
|
||||
savedCodec.PayloadType = codec.PayloadType
|
||||
}
|
||||
if savedCodec.Name == "" {
|
||||
savedCodec.Name = codec.Name
|
||||
}
|
||||
if savedCodec.ClockRate == 0 {
|
||||
savedCodec.ClockRate = codec.ClockRate
|
||||
}
|
||||
if savedCodec.EncodingParameters == "" {
|
||||
savedCodec.EncodingParameters = codec.EncodingParameters
|
||||
}
|
||||
if savedCodec.Fmtp == "" {
|
||||
savedCodec.Fmtp = codec.Fmtp
|
||||
}
|
||||
savedCodec.RTCPFeedback = append(savedCodec.RTCPFeedback, codec.RTCPFeedback...)
|
||||
|
||||
codecs[savedCodec.PayloadType] = savedCodec
|
||||
}
|
||||
|
||||
func (s *SessionDescription) buildCodecMap() map[uint8]Codec {
|
||||
codecs := map[uint8]Codec{
|
||||
// static codecs that do not require a rtpmap
|
||||
0: {
|
||||
PayloadType: 0,
|
||||
Name: "PCMU",
|
||||
ClockRate: 8000,
|
||||
},
|
||||
8: {
|
||||
PayloadType: 8,
|
||||
Name: "PCMA",
|
||||
ClockRate: 8000,
|
||||
},
|
||||
}
|
||||
|
||||
wildcardRTCPFeedback := []string{}
|
||||
for _, m := range s.MediaDescriptions {
|
||||
for _, a := range m.Attributes {
|
||||
attr := a.String()
|
||||
switch {
|
||||
case strings.HasPrefix(attr, "rtpmap:"):
|
||||
codec, err := parseRtpmap(attr)
|
||||
if err == nil {
|
||||
mergeCodecs(codec, codecs)
|
||||
}
|
||||
case strings.HasPrefix(attr, "fmtp:"):
|
||||
codec, err := parseFmtp(attr)
|
||||
if err == nil {
|
||||
mergeCodecs(codec, codecs)
|
||||
}
|
||||
case strings.HasPrefix(attr, "rtcp-fb:"):
|
||||
codec, isWildcard, err := parseRtcpFb(attr)
|
||||
switch {
|
||||
case err != nil:
|
||||
case isWildcard:
|
||||
wildcardRTCPFeedback = append(wildcardRTCPFeedback, codec.RTCPFeedback...)
|
||||
default:
|
||||
mergeCodecs(codec, codecs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, codec := range codecs {
|
||||
for _, newRTCPFeedback := range wildcardRTCPFeedback {
|
||||
codec.appendRTCPFeedback(newRTCPFeedback)
|
||||
}
|
||||
|
||||
codecs[i] = codec
|
||||
}
|
||||
|
||||
return codecs
|
||||
}
|
||||
|
||||
func equivalentFmtp(want, got string) bool {
|
||||
wantSplit := strings.Split(want, ";")
|
||||
gotSplit := strings.Split(got, ";")
|
||||
|
||||
if len(wantSplit) != len(gotSplit) {
|
||||
return false
|
||||
}
|
||||
|
||||
sort.Strings(wantSplit)
|
||||
sort.Strings(gotSplit)
|
||||
|
||||
for i, wantPart := range wantSplit {
|
||||
wantPart = strings.TrimSpace(wantPart)
|
||||
gotPart := strings.TrimSpace(gotSplit[i])
|
||||
if gotPart != wantPart {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func codecsMatch(wanted, got Codec) bool {
|
||||
if wanted.Name != "" && !strings.EqualFold(wanted.Name, got.Name) {
|
||||
return false
|
||||
}
|
||||
if wanted.ClockRate != 0 && wanted.ClockRate != got.ClockRate {
|
||||
return false
|
||||
}
|
||||
if wanted.EncodingParameters != "" && wanted.EncodingParameters != got.EncodingParameters {
|
||||
return false
|
||||
}
|
||||
if wanted.Fmtp != "" && !equivalentFmtp(wanted.Fmtp, got.Fmtp) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetCodecForPayloadType scans the SessionDescription for the given payload type and returns the codec
|
||||
func (s *SessionDescription) GetCodecForPayloadType(payloadType uint8) (Codec, error) {
|
||||
codecs := s.buildCodecMap()
|
||||
|
||||
codec, ok := codecs[payloadType]
|
||||
if ok {
|
||||
return codec, nil
|
||||
}
|
||||
|
||||
return codec, errPayloadTypeNotFound
|
||||
}
|
||||
|
||||
// GetPayloadTypeForCodec scans the SessionDescription for a codec that matches the provided codec
|
||||
// as closely as possible and returns its payload type
|
||||
func (s *SessionDescription) GetPayloadTypeForCodec(wanted Codec) (uint8, error) {
|
||||
codecs := s.buildCodecMap()
|
||||
|
||||
for payloadType, codec := range codecs {
|
||||
if codecsMatch(wanted, codec) {
|
||||
return payloadType, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, errCodecNotFound
|
||||
}
|
||||
|
||||
type stateFn func(*lexer) (stateFn, error)
|
||||
|
||||
type lexer struct {
|
||||
desc *SessionDescription
|
||||
cache *unmarshalCache
|
||||
baseLexer
|
||||
}
|
||||
|
||||
type keyToState func(key byte) stateFn
|
||||
|
||||
func (l *lexer) handleType(fn keyToState) (stateFn, error) {
|
||||
key, err := l.readType()
|
||||
if errors.Is(err, io.EOF) && key == 0 {
|
||||
return nil, nil //nolint:nilnil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res := fn(key); res != nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return nil, l.syntaxError()
|
||||
}
|
||||
Reference in New Issue
Block a user