1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

TEST: Upgrade pion to v3.2.9. (#3567)

------

Co-authored-by: chundonglinlin <chundonglinlin@163.com>
This commit is contained in:
Winlin 2023-06-05 11:25:04 +08:00 committed by GitHub
parent 104cf14d68
commit df854339ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1383 changed files with 118469 additions and 41421 deletions

View file

@ -1 +1,25 @@
### 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

View file

@ -5,18 +5,31 @@ linters-settings:
locale: US
exhaustive:
default-signifies-exhaustive: true
gomodguard:
blocked:
modules:
- github.com/pkg/errors:
recommendations:
- errors
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
- deadcode # Finds unused code
- contextcheck # check the function whether use a non-inherited context
- decorder # check declaration order and count of types, constants, variables and functions
- depguard # Go linter that checks if package imports are in a list of acceptable packages
- 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
- 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
@ -29,43 +42,64 @@ linters:
- 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
- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes
- 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
- nakedret # Finds naked returns in functions greater than a specified function length
- 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
- scopelint # Scopelint checks for unpinned variables in go programs
- 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
- structcheck # Finds unused struct fields
- 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
- varcheck # Finds unused global variables and constants
- wastedassign # wastedassign finds wasted assignment statements
- whitespace # Tool for detection of leading and trailing whitespace
disable:
- 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
- forbidigo # Forbids identifiers
- 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
- 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, better to be self contained
- path: _test\.go

View file

@ -0,0 +1,2 @@
builds:
- skip: true

View file

@ -0,0 +1,33 @@
# Thank you to everyone that made Pion possible. If you are interested in contributing
# we would love to have you https://github.com/pion/webrtc/wiki/Contributing
#
# This file is auto generated, using git to list all individuals contributors.
# see https://github.com/pion/.goassets/blob/master/scripts/generate-authors.sh for the scripting
Aaron France <aaron.l.france@gmail.com>
Adrian Cable <adrian.cable@gmail.com>
Atsushi Watanabe <atsushi.w@ieee.org>
backkem <mail@backkem.me>
Cecylia Bocovich <cohosh@torproject.org>
chenkaiC4 <chenkaic4@gmail.com>
Eric Daniels <eric@erdaniels.com>
Hugo Arregui <hugo.arregui@gmail.com>
Hugo Arregui <hugo@decentraland.org>
Jerko Steiner <jerko.steiner@gmail.com>
Jerry Tao <taojay315@gmail.com>
John Bradley <jrb@turrettech.com>
Konstantin Itskov <konstantin.itskov@kovits.com>
Lukas Herman <lherman.cs@gmail.com>
Luke Curley <lcurley@twitch.tv>
Michael MacDonald <github@macdonald.cx>
ronan <ronan.jezequel@gmail.com>
Sam Lancia <sam@vaion.com>
Sean DuBois <seaduboi@amazon.com>
Sean DuBois <sean@siobud.com>
Steffen Vogel <post@steffenvogel.de>
Teddy <richardgeorgeoff@gmail.com>
Will Forcey <wsforc3y@gmail.com>
Yutaka Takeda <yt0916@gmail.com>
ZHENK <chengzhenyang@gmail.com>
# List of contributors not appearing in Git history

View file

@ -1,20 +0,0 @@
<h1 align="center">
Design
</h1>
### Portable
Pion SCTP is written in Go and extremely portable. Anywhere Golang runs, Pion SCTP should work as well! Instead of dealing with complicated
cross-compiling of multiple libraries, you now can run anywhere with one `go build`
### Simple API
The API is based on an io.ReadWriteCloser.
### Readable
If code comes from an RFC we try to make sure everything is commented with a link to the spec.
This makes learning and debugging easier, this library was written to also serve as a guide for others.
### Tested
Every commit is tested via travis-ci Go provides fantastic facilities for testing, and more will be added as time goes on.
### Shared libraries
Every pion product is built using shared libraries, allowing others to review and reuse our libraries.

View file

@ -6,49 +6,29 @@
<h4 align="center">A Go implementation of SCTP</h4>
<p align="center">
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-sctp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion SCTP"></a>
<!--<a href="https://sourcegraph.com/github.com/pion/webrtc?badge"><img src="https://sourcegraph.com/github.com/pion/webrtc/-/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>
<a href="https://travis-ci.org/pion/sctp"><img src="https://travis-ci.org/pion/sctp.svg?branch=master" alt="Build Status"></a>
<a href="https://pkg.go.dev/github.com/pion/sctp"><img src="https://godoc.org/github.com/pion/sctp?status.svg" alt="GoDoc"></a>
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/sctp/test.yaml">
<a href="https://pkg.go.dev/github.com/pion/sctp"><img src="https://pkg.go.dev/badge/github.com/pion/sctp.svg" alt="Go Reference"></a>
<a href="https://codecov.io/gh/pion/sctp"><img src="https://codecov.io/gh/pion/sctp/branch/master/graph/badge.svg" alt="Coverage Status"></a>
<a href="https://goreportcard.com/report/github.com/pion/sctp"><img src="https://goreportcard.com/badge/github.com/pion/sctp" alt="Go Report Card"></a>
<!--<a href="https://www.codacy.com/app/Sean-Der/webrtc"><img src="https://api.codacy.com/project/badge/Grade/18f4aec384894e6aac0b94effe51961d" alt="Codacy Badge"></a>-->
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
</p>
<br>
See [DESIGN.md](DESIGN.md) for an overview of features and future goals.
### Roadmap
The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
### Community
Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion).
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:
* [John Bradley](https://github.com/kc5nra) - *Original Author*
* [Sean DuBois](https://github.com/Sean-Der) - *Original Author*
* [Michiel De Backker](https://github.com/backkem) - *Public API, Initialization*
* [Konstantin Itskov](https://github.com/trivigy) - *Fix documentation*
* [chenkaiC4](https://github.com/chenkaiC4) - *Fix GolangCI Linter*
* [Ronan J](https://github.com/ronanj) - *Fix PPID*
* [Michael MacDonald](https://github.com/mjmac) - *Fix races*
* [Yutaka Takeda](https://github.com/enobufs) - *PR-SCTP, Retransmissions, Congestion Control*
* [Antoine Baché](https://github.com/Antonito) - *SCTP Profiling*
* [Cecylia Bocovich](https://github.com/cohosh) - *Fix SCTP reads*
* [Hugo Arregui](https://github.com/hugoArregui)
* [Atsushi Watanabe](https://github.com/at-wat)
* [Lukas Herman](https://github.com/lherman-cs)
* [Luke Curley](https://github.com/kixelated) - *Performance*
* [Aaron France](https://github.com/AeroNotix)
* [ZHENK](https://github.com/scorpionknifes)
Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt)
### License
MIT License - see [LICENSE](LICENSE) for full text

View file

@ -2,6 +2,8 @@ package sctp
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"math"
@ -12,13 +14,33 @@ import (
"github.com/pion/logging"
"github.com/pion/randutil"
"github.com/pkg/errors"
)
// Use global random generator to properly seed by crypto grade random.
var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals
// Association errors
var (
globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals
errChunk = errors.New("Abort chunk, with following errors")
ErrChunk = errors.New("abort chunk, with following errors")
ErrShutdownNonEstablished = errors.New("shutdown called in non-established state")
ErrAssociationClosedBeforeConn = errors.New("association closed before connecting")
ErrSilentlyDiscard = errors.New("silently discard")
ErrInitNotStoredToSend = errors.New("the init not stored to send")
ErrCookieEchoNotStoredToSend = errors.New("cookieEcho not stored to send")
ErrSCTPPacketSourcePortZero = errors.New("sctp packet must not have a source port of 0")
ErrSCTPPacketDestinationPortZero = errors.New("sctp packet must not have a destination port of 0")
ErrInitChunkBundled = errors.New("init chunk must not be bundled with any other chunk")
ErrInitChunkVerifyTagNotZero = errors.New("init chunk expects a verification tag of 0 on the packet when out-of-the-blue")
ErrHandleInitState = errors.New("todo: handle Init when in state")
ErrInitAckNoCookie = errors.New("no cookie in InitAck")
ErrInflightQueueTSNPop = errors.New("unable to be popped from inflight queue TSN")
ErrTSNRequestNotExist = errors.New("requested non-existent TSN")
ErrResetPacketInStateNotExist = errors.New("sending reset packet in non-established state")
ErrParamterType = errors.New("unexpected parameter type")
ErrPayloadDataStateNotExist = errors.New("sending payload data in non-established state")
ErrChunkTypeUnhandled = errors.New("unhandled chunk type")
ErrHandshakeInitAck = errors.New("handshake failed (INIT ACK)")
ErrHandshakeCookieEcho = errors.New("handshake failed (COOKIE ECHO)")
)
const (
@ -46,6 +68,7 @@ const (
const (
timerT1Init int = iota
timerT1Cookie
timerT2Shutdown
timerT3RTX
timerReconfig
)
@ -60,8 +83,8 @@ const (
// ack transmission state
const (
ackStateIdle int = iota // ack timer is off
ackStateImmediate // ack timer is on (ack is being delayed)
ackStateDelay // will send ack immediately
ackStateImmediate // will send ack immediately
ackStateDelay // ack timer is on (ack is being delayed)
)
// other constants
@ -94,21 +117,17 @@ func getAssociationStateString(a uint32) string {
// Association represents an SCTP association
// 13.2. Parameters Necessary per Association (i.e., the TCB)
// Peer : Tag value to be sent in every packet and is received
// Verification: in the INIT or INIT ACK chunk.
// Tag :
//
// My : Tag expected in every inbound packet and sent in the
// Verification: INIT or INIT ACK chunk.
// Peer : Tag value to be sent in every packet and is received
// Verification: in the INIT or INIT ACK chunk.
// Tag :
// State : A state variable indicating what state the association
// : is in, i.e., COOKIE-WAIT, COOKIE-ECHOED, ESTABLISHED,
// : SHUTDOWN-PENDING, SHUTDOWN-SENT, SHUTDOWN-RECEIVED,
// : SHUTDOWN-ACK-SENT.
//
// Tag :
// State : A state variable indicating what state the association
// : is in, i.e., COOKIE-WAIT, COOKIE-ECHOED, ESTABLISHED,
// : SHUTDOWN-PENDING, SHUTDOWN-SENT, SHUTDOWN-RECEIVED,
// : SHUTDOWN-ACK-SENT.
//
// Note: No "CLOSED" state is illustrated since if a
// association is "CLOSED" its TCB SHOULD be removed.
// Note: No "CLOSED" state is illustrated since if a
// association is "CLOSED" its TCB SHOULD be removed.
type Association struct {
bytesReceived uint64
bytesSent uint64
@ -127,6 +146,13 @@ type Association struct {
willRetransmitFast bool
willRetransmitReconfig bool
willSendShutdown bool
willSendShutdownAck bool
willSendShutdownComplete bool
willSendAbort bool
willSendAbortCause errorCause
// Reconfig
myNextRSN uint32
reconfigs map[uint32]*chunkReconfig
@ -143,7 +169,8 @@ type Association struct {
pendingQueue *pendingQueue
controlQueue *controlQueue
mtu uint32
maxPayloadSize uint32 // max DATA chunk payload size
maxPayloadSize uint32 // max DATA chunk payload size
srtt atomic.Value // type float64
cumulativeTSNAckPoint uint32
advancedPeerTSNAckPoint uint32
useForwardTSN bool
@ -159,12 +186,13 @@ type Association struct {
fastRecoverExitPoint uint32
// RTX & Ack timer
rtoMgr *rtoManager
t1Init *rtxTimer
t1Cookie *rtxTimer
t3RTX *rtxTimer
tReconfig *rtxTimer
ackTimer *ackTimer
rtoMgr *rtoManager
t1Init *rtxTimer
t1Cookie *rtxTimer
t2Shutdown *rtxTimer
t3RTX *rtxTimer
tReconfig *rtxTimer
ackTimer *ackTimer
// Chunks stored for retransmission
storedInit *chunkInit
@ -217,7 +245,7 @@ func Server(config Config) (*Association, error) {
}
return a, nil
case <-a.readLoopCloseCh:
return nil, errors.Errorf("association closed before connecting")
return nil, ErrAssociationClosedBeforeConn
}
}
@ -233,7 +261,7 @@ func Client(config Config) (*Association, error) {
}
return a, nil
case <-a.readLoopCloseCh:
return nil, errors.Errorf("association closed before connecting")
return nil, ErrAssociationClosedBeforeConn
}
}
@ -281,7 +309,7 @@ func createAssociation(config Config) *Association {
handshakeCompletedCh: make(chan error),
cumulativeTSNAckPoint: tsn - 1,
advancedPeerTSNAckPoint: tsn - 1,
silentError: errors.Errorf("silently discard"),
silentError: ErrSilentlyDiscard,
stats: &associationStats{},
log: config.LoggerFactory.NewLogger("sctp"),
}
@ -296,10 +324,12 @@ func createAssociation(config Config) *Association {
a.log.Tracef("[%s] updated cwnd=%d ssthresh=%d inflight=%d (INI)",
a.name, a.cwnd, a.ssthresh, a.inflightQueue.getNumBytes())
a.srtt.Store(float64(0))
a.t1Init = newRTXTimer(timerT1Init, a, maxInitRetrans)
a.t1Cookie = newRTXTimer(timerT1Cookie, a, maxInitRetrans)
a.t3RTX = newRTXTimer(timerT3RTX, a, noMaxRetrans) // retransmit forever
a.tReconfig = newRTXTimer(timerReconfig, a, noMaxRetrans) // retransmit forever
a.t2Shutdown = newRTXTimer(timerT2Shutdown, a, noMaxRetrans) // retransmit forever
a.t3RTX = newRTXTimer(timerT3RTX, a, noMaxRetrans) // retransmit forever
a.tReconfig = newRTXTimer(timerReconfig, a, noMaxRetrans) // retransmit forever
a.ackTimer = newAckTimer(a)
return a
@ -336,7 +366,7 @@ func (a *Association) init(isClient bool) {
func (a *Association) sendInit() error {
a.log.Debugf("[%s] sending INIT", a.name)
if a.storedInit == nil {
return errors.Errorf("the init not stored to send")
return ErrInitNotStoredToSend
}
outbound := &packet{}
@ -357,7 +387,7 @@ func (a *Association) sendInit() error {
// caller must hold a.lock
func (a *Association) sendCookieEcho() error {
if a.storedCookieEcho == nil {
return errors.Errorf("cookieEcho not stored to send")
return ErrCookieEchoNotStoredToSend
}
a.log.Debugf("[%s] sending COOKIE-ECHO", a.name)
@ -374,10 +404,62 @@ func (a *Association) sendCookieEcho() error {
return nil
}
// Shutdown initiates the shutdown sequence. The method blocks until the
// shutdown sequence is completed and the connection is closed, or until the
// passed context is done, in which case the context's error is returned.
func (a *Association) Shutdown(ctx context.Context) error {
a.log.Debugf("[%s] closing association..", a.name)
state := a.getState()
if state != established {
return fmt.Errorf("%w: shutdown %s", ErrShutdownNonEstablished, a.name)
}
// Attempt a graceful shutdown.
a.setState(shutdownPending)
a.lock.Lock()
if a.inflightQueue.size() == 0 {
// No more outstanding, send shutdown.
a.willSendShutdown = true
a.awakeWriteLoop()
a.setState(shutdownSent)
}
a.lock.Unlock()
select {
case <-a.closeWriteLoopCh:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// Close ends the SCTP Association and cleans up any state
func (a *Association) Close() error {
a.log.Debugf("[%s] closing association..", a.name)
err := a.close()
// Wait for readLoop to end
<-a.readLoopCloseCh
a.log.Debugf("[%s] association closed", a.name)
a.log.Debugf("[%s] stats nDATAs (in) : %d", a.name, a.stats.getNumDATAs())
a.log.Debugf("[%s] stats nSACKs (in) : %d", a.name, a.stats.getNumSACKs())
a.log.Debugf("[%s] stats nT3Timeouts : %d", a.name, a.stats.getNumT3Timeouts())
a.log.Debugf("[%s] stats nAckTimeouts: %d", a.name, a.stats.getNumAckTimeouts())
a.log.Debugf("[%s] stats nFastRetrans: %d", a.name, a.stats.getNumFastRetrans())
return err
}
func (a *Association) close() error {
a.log.Debugf("[%s] closing association..", a.name)
a.setState(closed)
err := a.netConn.Close()
@ -387,22 +469,34 @@ func (a *Association) Close() error {
// awake writeLoop to exit
a.closeWriteLoopOnce.Do(func() { close(a.closeWriteLoopCh) })
return err
}
// Abort sends the abort packet with user initiated abort and immediately
// closes the connection.
func (a *Association) Abort(reason string) {
a.log.Debugf("[%s] aborting association: %s", a.name, reason)
a.lock.Lock()
a.willSendAbort = true
a.willSendAbortCause = &errorCauseUserInitiatedAbort{
upperLayerAbortReason: []byte(reason),
}
a.lock.Unlock()
a.awakeWriteLoop()
// Wait for readLoop to end
<-a.readLoopCloseCh
a.log.Debugf("[%s] association closed", a.name)
a.log.Debugf("[%s] stats nDATAs (in) : %d", a.name, a.stats.getNumDATAs())
a.log.Debugf("[%s] stats nSACKs (in) : %d", a.name, a.stats.getNumSACKs())
a.log.Debugf("[%s] stats nT3Timeouts : %d", a.name, a.stats.getNumT3Timeouts())
a.log.Debugf("[%s] stats nAckTimeouts: %d", a.name, a.stats.getNumAckTimeouts())
a.log.Debugf("[%s] stats nFastRetrans: %d", a.name, a.stats.getNumFastRetrans())
return err
}
func (a *Association) closeAllTimers() {
// Close all retransmission & ack timers
a.t1Init.close()
a.t1Cookie.close()
a.t2Shutdown.close()
a.t3RTX.close()
a.tReconfig.close()
a.ackTimer.close()
@ -422,6 +516,13 @@ func (a *Association) readLoop() {
a.lock.Unlock()
close(a.acceptCh)
close(a.readLoopCloseCh)
a.log.Debugf("[%s] association closed", a.name)
a.log.Debugf("[%s] stats nDATAs (in) : %d", a.name, a.stats.getNumDATAs())
a.log.Debugf("[%s] stats nSACKs (in) : %d", a.name, a.stats.getNumSACKs())
a.log.Debugf("[%s] stats nT3Timeouts : %d", a.name, a.stats.getNumT3Timeouts())
a.log.Debugf("[%s] stats nAckTimeouts: %d", a.name, a.stats.getNumAckTimeouts())
a.log.Debugf("[%s] stats nFastRetrans: %d", a.name, a.stats.getNumFastRetrans())
}()
a.log.Debugf("[%s] readLoop entered", a.name)
@ -451,15 +552,16 @@ func (a *Association) readLoop() {
func (a *Association) writeLoop() {
a.log.Debugf("[%s] writeLoop entered", a.name)
defer a.log.Debugf("[%s] writeLoop exited", a.name)
loop:
for {
rawPackets := a.gatherOutbound()
rawPackets, ok := a.gatherOutbound()
for _, raw := range rawPackets {
_, err := a.netConn.Write(raw)
if err != nil {
if err != io.EOF {
if !errors.Is(err, io.EOF) {
a.log.Warnf("[%s] failed to write packets on netConn: %v", a.name, err)
}
a.log.Debugf("[%s] writeLoop ended", a.name)
@ -468,6 +570,14 @@ loop:
atomic.AddUint64(&a.bytesSent, uint64(len(raw)))
}
if !ok {
if err := a.close(); err != nil {
a.log.Warnf("[%s] failed to close association: %v", a.name, err)
}
return
}
select {
case <-a.awakeWriteLoopCh:
case <-a.closeWriteLoopCh:
@ -477,8 +587,6 @@ loop:
a.setState(closed)
a.closeAllTimers()
a.log.Debugf("[%s] writeLoop exited", a.name)
}
func (a *Association) awakeWriteLoop() {
@ -526,7 +634,7 @@ func (a *Association) handleInbound(raw []byte) error {
}
// The caller should hold the lock
func (a *Association) gatherOutboundDataAndReconfigPackets(rawPackets [][]byte) [][]byte {
func (a *Association) gatherDataPacketsToRetransmit(rawPackets [][]byte) [][]byte {
for _, p := range a.getDataPacketsToRetransmit() {
raw, err := p.marshal()
if err != nil {
@ -536,6 +644,11 @@ func (a *Association) gatherOutboundDataAndReconfigPackets(rawPackets [][]byte)
rawPackets = append(rawPackets, raw)
}
return rawPackets
}
// The caller should hold the lock
func (a *Association) gatherOutboundDataAndReconfigPackets(rawPackets [][]byte) [][]byte {
// Pop unsent data chunks from the pending queue to send as much as
// cwnd and rwnd allow.
chunks, sisToReset := a.popPendingDataChunksToSend()
@ -599,7 +712,7 @@ func (a *Association) gatherOutboundDataAndReconfigPackets(rawPackets [][]byte)
}
// The caller should hold the lock
func (a *Association) gatherOutboundFrastRetransmissionPackets(rawPackets [][]byte) [][]byte {
func (a *Association) gatherOutboundFastRetransmissionPackets(rawPackets [][]byte) [][]byte {
if a.willRetransmitFast {
a.willRetransmitFast = false
@ -662,7 +775,7 @@ func (a *Association) gatherOutboundSackPackets(rawPackets [][]byte) [][]byte {
if a.ackState == ackStateImmediate {
a.ackState = ackStateIdle
sack := a.createSelectiveAckChunk()
a.log.Debugf("[%s] sending SACK: %s", a.name, sack.String())
a.log.Debugf("[%s] sending SACK: %s", a.name, sack)
raw, err := a.createPacket([]chunk{sack}).marshal()
if err != nil {
a.log.Warnf("[%s] failed to serialize a SACK packet", a.name)
@ -692,11 +805,86 @@ func (a *Association) gatherOutboundForwardTSNPackets(rawPackets [][]byte) [][]b
return rawPackets
}
// gatherOutbound gathers outgoing packets
func (a *Association) gatherOutbound() [][]byte {
func (a *Association) gatherOutboundShutdownPackets(rawPackets [][]byte) ([][]byte, bool) {
ok := true
switch {
case a.willSendShutdown:
a.willSendShutdown = false
shutdown := &chunkShutdown{
cumulativeTSNAck: a.cumulativeTSNAckPoint,
}
raw, err := a.createPacket([]chunk{shutdown}).marshal()
if err != nil {
a.log.Warnf("[%s] failed to serialize a Shutdown packet", a.name)
} else {
a.t2Shutdown.start(a.rtoMgr.getRTO())
rawPackets = append(rawPackets, raw)
}
case a.willSendShutdownAck:
a.willSendShutdownAck = false
shutdownAck := &chunkShutdownAck{}
raw, err := a.createPacket([]chunk{shutdownAck}).marshal()
if err != nil {
a.log.Warnf("[%s] failed to serialize a ShutdownAck packet", a.name)
} else {
a.t2Shutdown.start(a.rtoMgr.getRTO())
rawPackets = append(rawPackets, raw)
}
case a.willSendShutdownComplete:
a.willSendShutdownComplete = false
shutdownComplete := &chunkShutdownComplete{}
raw, err := a.createPacket([]chunk{shutdownComplete}).marshal()
if err != nil {
a.log.Warnf("[%s] failed to serialize a ShutdownComplete packet", a.name)
} else {
rawPackets = append(rawPackets, raw)
ok = false
}
}
return rawPackets, ok
}
func (a *Association) gatherAbortPacket() ([]byte, error) {
cause := a.willSendAbortCause
a.willSendAbort = false
a.willSendAbortCause = nil
abort := &chunkAbort{}
if cause != nil {
abort.errorCauses = []errorCause{cause}
}
raw, err := a.createPacket([]chunk{abort}).marshal()
return raw, err
}
// gatherOutbound gathers outgoing packets. The returned bool value set to
// false means the association should be closed down after the final send.
func (a *Association) gatherOutbound() ([][]byte, bool) {
a.lock.Lock()
defer a.lock.Unlock()
if a.willSendAbort {
pkt, err := a.gatherAbortPacket()
if err != nil {
a.log.Warnf("[%s] failed to serialize an abort packet", a.name)
return nil, false
}
return [][]byte{pkt}, false
}
rawPackets := [][]byte{}
if a.controlQueue.size() > 0 {
@ -712,14 +900,25 @@ func (a *Association) gatherOutbound() [][]byte {
state := a.getState()
if state == established {
ok := true
switch state {
case established:
rawPackets = a.gatherDataPacketsToRetransmit(rawPackets)
rawPackets = a.gatherOutboundDataAndReconfigPackets(rawPackets)
rawPackets = a.gatherOutboundFrastRetransmissionPackets(rawPackets)
rawPackets = a.gatherOutboundFastRetransmissionPackets(rawPackets)
rawPackets = a.gatherOutboundSackPackets(rawPackets)
rawPackets = a.gatherOutboundForwardTSNPackets(rawPackets)
case shutdownPending, shutdownSent, shutdownReceived:
rawPackets = a.gatherDataPacketsToRetransmit(rawPackets)
rawPackets = a.gatherOutboundFastRetransmissionPackets(rawPackets)
rawPackets = a.gatherOutboundSackPackets(rawPackets)
rawPackets, ok = a.gatherOutboundShutdownPackets(rawPackets)
case shutdownAckSent:
rawPackets, ok = a.gatherOutboundShutdownPackets(rawPackets)
}
return rawPackets
return rawPackets, ok
}
func checkPacket(p *packet) error {
@ -731,7 +930,7 @@ func checkPacket(p *packet) error {
// identify the association to which this packet belongs. The port
// number 0 MUST NOT be used.
if p.sourcePort == 0 {
return errors.Errorf("sctp packet must not have a source port of 0")
return ErrSCTPPacketSourcePortZero
}
// This is the SCTP port number to which this packet is destined.
@ -739,7 +938,7 @@ func checkPacket(p *packet) error {
// SCTP packet to the correct receiving endpoint/application. The
// port number 0 MUST NOT be used.
if p.destinationPort == 0 {
return errors.Errorf("sctp packet must not have a destination port of 0")
return ErrSCTPPacketDestinationPortZero
}
// Check values on the packet that are specific to a particular chunk type
@ -750,13 +949,13 @@ func checkPacket(p *packet) error {
// They MUST be the only chunks present in the SCTP packets that carry
// them.
if len(p.chunks) != 1 {
return errors.Errorf("init chunk must not be bundled with any other chunk")
return ErrInitChunkBundled
}
// A packet containing an INIT chunk MUST have a zero Verification
// Tag.
if p.verificationTag != 0 {
return errors.Errorf("init chunk expects a verification tag of 0 on the packet when out-of-the-blue")
return ErrInitChunkVerifyTagNotZero
}
}
}
@ -812,6 +1011,26 @@ func (a *Association) BytesReceived() uint64 {
return atomic.LoadUint64(&a.bytesReceived)
}
// MTU returns the association's current MTU
func (a *Association) MTU() uint32 {
return atomic.LoadUint32(&a.mtu)
}
// CWND returns the association's current congestion window (cwnd)
func (a *Association) CWND() uint32 {
return atomic.LoadUint32(&a.cwnd)
}
// RWND returns the association's current receiver window (rwnd)
func (a *Association) RWND() uint32 {
return atomic.LoadUint32(&a.rwnd)
}
// SRTT returns the latest smoothed round-trip time (srrt)
func (a *Association) SRTT() float64 {
return a.srtt.Load().(float64) //nolint:forcetypeassert
}
func setSupportedExtensions(init *chunkInitCommon) {
// nolint:godox
// TODO RFC5061 https://tools.ietf.org/html/rfc6525#section-5.2
@ -838,7 +1057,7 @@ func (a *Association) handleInit(p *packet, i *chunkInit) ([]*packet, error) {
if state != closed && state != cookieWait && state != cookieEchoed {
// 5.2.2. Unexpected INIT in States Other than CLOSED, COOKIE-ECHOED,
// COOKIE-WAIT, and SHUTDOWN-ACK-SENT
return nil, errors.Errorf("todo: handle Init when in state %s", getAssociationStateString(state))
return nil, fmt.Errorf("%w: %s", ErrHandleInitState, getAssociationStateString(state))
}
// Should we be setting any of these permanently until we've ACKed further?
@ -859,14 +1078,14 @@ func (a *Association) handleInit(p *packet, i *chunkInit) ([]*packet, error) {
case *paramSupportedExtensions:
for _, t := range v.ChunkTypes {
if t == ctForwardTSN {
a.log.Debugf("[%s] use ForwardTSN (on init)\n", a.name)
a.log.Debugf("[%s] use ForwardTSN (on init)", a.name)
a.useForwardTSN = true
}
}
}
}
if !a.useForwardTSN {
a.log.Warnf("[%s] not using ForwardTSN (on init)\n", a.name)
a.log.Warnf("[%s] not using ForwardTSN (on init)", a.name)
}
outbound := &packet{}
@ -944,17 +1163,17 @@ func (a *Association) handleInitAck(p *packet, i *chunkInitAck) error {
case *paramSupportedExtensions:
for _, t := range v.ChunkTypes {
if t == ctForwardTSN {
a.log.Debugf("[%s] use ForwardTSN (on initAck)\n", a.name)
a.log.Debugf("[%s] use ForwardTSN (on initAck)", a.name)
a.useForwardTSN = true
}
}
}
}
if !a.useForwardTSN {
a.log.Warnf("[%s] not using ForwardTSN (on initAck)\n", a.name)
a.log.Warnf("[%s] not using ForwardTSN (on initAck)", a.name)
}
if cookieParam == nil {
return errors.Errorf("no cookie in InitAck")
return ErrInitAckNoCookie
}
a.storedCookieEcho = &chunkCookieEcho{}
@ -996,6 +1215,11 @@ func (a *Association) handleHeartbeat(c *chunkHeartbeat) []*packet {
func (a *Association) handleCookieEcho(c *chunkCookieEcho) []*packet {
state := a.getState()
a.log.Debugf("[%s] COOKIE-ECHO received in state '%s'", a.name, getAssociationStateString(state))
if a.myCookie == nil {
a.log.Debugf("[%s] COOKIE-ECHO received before initialization", a.name)
return nil
}
switch state {
default:
return nil
@ -1054,7 +1278,7 @@ func (a *Association) handleData(d *chunkPayloadData) []*packet {
canPush := a.payloadQueue.canPush(d, a.peerLastTSN)
if canPush {
s := a.getOrCreateStream(d.streamIdentifier)
s := a.getOrCreateStream(d.streamIdentifier, true, PayloadTypeUnknown)
if s == nil {
// silentely discard the data. (sender will retry on T3-rtx timeout)
// see pion/sctp#30
@ -1145,14 +1369,7 @@ func (a *Association) OpenStream(streamIdentifier uint16, defaultPayloadType Pay
a.lock.Lock()
defer a.lock.Unlock()
if _, ok := a.streams[streamIdentifier]; ok {
return nil, errors.Errorf("there already exists a stream with identifier %d", streamIdentifier)
}
s := a.createStream(streamIdentifier, false)
s.setDefaultPayloadType(defaultPayloadType)
return s, nil
return a.getOrCreateStream(streamIdentifier, false, defaultPayloadType), nil
}
// AcceptStream accepts a stream
@ -1195,12 +1412,17 @@ func (a *Association) createStream(streamIdentifier uint16, accept bool) *Stream
}
// getOrCreateStream gets or creates a stream. The caller should hold the lock.
func (a *Association) getOrCreateStream(streamIdentifier uint16) *Stream {
func (a *Association) getOrCreateStream(streamIdentifier uint16, accept bool, defaultPayloadType PayloadProtocolIdentifier) *Stream {
if s, ok := a.streams[streamIdentifier]; ok {
s.SetDefaultPayloadType(defaultPayloadType)
return s
}
return a.createStream(streamIdentifier, true)
s := a.createStream(streamIdentifier, accept)
if s != nil {
s.SetDefaultPayloadType(defaultPayloadType)
}
return s
}
// The caller should hold the lock.
@ -1213,7 +1435,7 @@ func (a *Association) processSelectiveAck(d *chunkSelectiveAck) (map[uint16]int,
for i := a.cumulativeTSNAckPoint + 1; sna32LTE(i, d.cumulativeTSNAck); i++ {
c, ok := a.inflightQueue.pop(i)
if !ok {
return nil, 0, errors.Errorf("tsn %v unable to be popped from inflight queue", i)
return nil, 0, fmt.Errorf("%w: %v", ErrInflightQueueTSNPop, i)
}
if !c.acked {
@ -1249,6 +1471,7 @@ func (a *Association) processSelectiveAck(d *chunkSelectiveAck) (map[uint16]int,
a.minTSN2MeasureRTT = a.myNextTSN
rtt := time.Since(c.since).Seconds() * 1000.0
srtt := a.rtoMgr.setNewRTT(rtt)
a.srtt.Store(srtt)
a.log.Tracef("[%s] SACK: measured-rtt=%f srtt=%f new-rto=%f",
a.name, rtt, srtt, a.rtoMgr.getRTO())
}
@ -1268,7 +1491,7 @@ func (a *Association) processSelectiveAck(d *chunkSelectiveAck) (map[uint16]int,
tsn := d.cumulativeTSNAck + uint32(i)
c, ok := a.inflightQueue.get(tsn)
if !ok {
return nil, 0, errors.Errorf("requested non-existent TSN %v", tsn)
return nil, 0, fmt.Errorf("%w: %v", ErrTSNRequestNotExist, tsn)
}
if !c.acked {
@ -1287,6 +1510,7 @@ func (a *Association) processSelectiveAck(d *chunkSelectiveAck) (map[uint16]int,
a.minTSN2MeasureRTT = a.myNextTSN
rtt := time.Since(c.since).Seconds() * 1000.0
srtt := a.rtoMgr.setNewRTT(rtt)
a.srtt.Store(srtt)
a.log.Tracef("[%s] SACK: measured-rtt=%f srtt=%f new-rto=%f",
a.name, rtt, srtt, a.rtoMgr.getRTO())
}
@ -1384,7 +1608,7 @@ func (a *Association) processFastRetransmission(cumTSNAckPoint, htna uint32, cum
for tsn := cumTSNAckPoint + 1; sna32LT(tsn, maxTSN); tsn++ {
c, ok := a.inflightQueue.get(tsn)
if !ok {
return errors.Errorf("requested non-existent TSN %v", tsn)
return fmt.Errorf("%w: %v", ErrTSNRequestNotExist, tsn)
}
if !c.acked && !c.abandoned() && c.missIndicator < 3 {
c.missIndicator++
@ -1419,7 +1643,7 @@ func (a *Association) processFastRetransmission(cumTSNAckPoint, htna uint32, cum
func (a *Association) handleSack(d *chunkSelectiveAck) error {
a.log.Tracef("[%s] SACK: cumTSN=%d a_rwnd=%d", a.name, d.cumulativeTSNAck, d.advertisedReceiverWindowCredit)
state := a.getState()
if state != established {
if state != established && state != shutdownPending && state != shutdownReceived {
return nil
}
@ -1518,19 +1742,94 @@ func (a *Association) handleSack(d *chunkSelectiveAck) error {
a.awakeWriteLoop()
}
if a.inflightQueue.size() > 0 {
a.postprocessSack(state, cumTSNAckPointAdvanced)
return nil
}
// The caller must hold the lock. This method was only added because the
// linter was complaining about the "cognitive complexity" of handleSack.
func (a *Association) postprocessSack(state uint32, shouldAwakeWriteLoop bool) {
switch {
case a.inflightQueue.size() > 0:
// Start timer. (noop if already started)
a.log.Tracef("[%s] T3-rtx timer start (pt3)", a.name)
a.t3RTX.start(a.rtoMgr.getRTO())
case state == shutdownPending:
// No more outstanding, send shutdown.
shouldAwakeWriteLoop = true
a.willSendShutdown = true
a.setState(shutdownSent)
case state == shutdownReceived:
// No more outstanding, send shutdown ack.
shouldAwakeWriteLoop = true
a.willSendShutdownAck = true
a.setState(shutdownAckSent)
}
if cumTSNAckPointAdvanced {
if shouldAwakeWriteLoop {
a.awakeWriteLoop()
}
}
// The caller should hold the lock.
func (a *Association) handleShutdown(_ *chunkShutdown) {
state := a.getState()
switch state {
case established:
if a.inflightQueue.size() > 0 {
a.setState(shutdownReceived)
} else {
// No more outstanding, send shutdown ack.
a.willSendShutdownAck = true
a.setState(shutdownAckSent)
a.awakeWriteLoop()
}
// a.cumulativeTSNAckPoint = c.cumulativeTSNAck
case shutdownSent:
a.willSendShutdownAck = true
a.setState(shutdownAckSent)
a.awakeWriteLoop()
}
}
// The caller should hold the lock.
func (a *Association) handleShutdownAck(_ *chunkShutdownAck) {
state := a.getState()
if state == shutdownSent || state == shutdownAckSent {
a.t2Shutdown.stop()
a.willSendShutdownComplete = true
a.awakeWriteLoop()
}
}
func (a *Association) handleShutdownComplete(_ *chunkShutdownComplete) error {
state := a.getState()
if state == shutdownAckSent {
a.t2Shutdown.stop()
return a.close()
}
return nil
}
func (a *Association) handleAbort(c *chunkAbort) error {
var errStr string
for _, e := range c.errorCauses {
errStr += fmt.Sprintf("(%s)", e)
}
_ = a.close()
return fmt.Errorf("[%s] %w: %s", a.name, ErrChunk, errStr)
}
// createForwardTSN generates ForwardTSN chunk.
// This method will be be called if useForwardTSN is set to false.
// The caller should hold the lock.
@ -1633,7 +1932,7 @@ func (a *Association) handleForwardTSN(c *chunkForwardTSN) []*packet {
// send a SACK to its peer (the sender of the FORWARD TSN) since such a
// duplicate may indicate the previous SACK was lost in the network.
a.log.Tracef("[%s] should send ack? newCumTSN=%d peerLastTSN=%d\n",
a.log.Tracef("[%s] should send ack? newCumTSN=%d peerLastTSN=%d",
a.name, c.newCumulativeTSN, a.peerLastTSN)
if sna32LTE(c.newCumulativeTSN, a.peerLastTSN) {
a.log.Tracef("[%s] sending ack on Forward TSN", a.name)
@ -1686,7 +1985,7 @@ func (a *Association) sendResetRequest(streamIdentifier uint16) error {
state := a.getState()
if state != established {
return errors.Errorf("sending reset packet in non-established state: state=%s",
return fmt.Errorf("%w: state=%s", ErrResetPacketInStateNotExist,
getAssociationStateString(state))
}
@ -1708,21 +2007,23 @@ func (a *Association) sendResetRequest(streamIdentifier uint16) error {
func (a *Association) handleReconfigParam(raw param) (*packet, error) {
switch p := raw.(type) {
case *paramOutgoingResetRequest:
a.log.Tracef("[%s] handleReconfigParam (OutgoingResetRequest)", a.name)
a.reconfigRequests[p.reconfigRequestSequenceNumber] = p
resp := a.resetStreamsIfAny(p)
if resp != nil {
return resp, nil
}
return nil, nil
return nil, nil //nolint:nilnil
case *paramReconfigResponse:
a.log.Tracef("[%s] handleReconfigParam (ReconfigResponse)", a.name)
delete(a.reconfigs, p.reconfigResponseSequenceNumber)
if len(a.reconfigs) == 0 {
a.tReconfig.stop()
}
return nil, nil
return nil, nil //nolint:nilnil
default:
return nil, errors.Errorf("unexpected parameter type %T", p)
return nil, fmt.Errorf("%w: %t", ErrParamterType, p)
}
}
@ -1737,7 +2038,11 @@ func (a *Association) resetStreamsIfAny(p *paramOutgoingResetRequest) *packet {
if !ok {
continue
}
a.unregisterStream(s, io.EOF)
a.lock.Unlock()
s.onInboundStreamReset()
a.lock.Lock()
a.log.Debugf("[%s] deleting stream %d", a.name, id)
delete(a.streams, s.streamIdentifier)
}
delete(a.reconfigRequests, p.reconfigRequestSequenceNumber)
} else {
@ -1777,7 +2082,6 @@ func (a *Association) movePendingDataChunkToInflightQueue(c *chunkPayloadData) {
a.log.Tracef("[%s] sending ppi=%d tsn=%d ssn=%d sent=%d len=%d (%v,%v)",
a.name, c.payloadType, c.tsn, c.streamSequenceNumber, c.nSent, len(c.userData), c.beginningFragment, c.endingFragment)
// Push it into the inflightQueue
a.inflightQueue.pushNoCheck(c)
}
@ -1880,7 +2184,7 @@ func (a *Association) sendPayloadData(chunks []*chunkPayloadData) error {
state := a.getState()
if state != established {
return errors.Errorf("sending payload data in non-established state: state=%s",
return fmt.Errorf("%w: state=%s", ErrPayloadDataStateNotExist,
getAssociationStateString(state))
}
@ -2014,7 +2318,6 @@ func (a *Association) handleChunkEnd() {
defer a.lock.Unlock()
if a.immediateAckTriggered {
// Send SACK now!
a.ackState = ackStateImmediate
a.ackTimer.stop()
a.awakeWriteLoop()
@ -2037,6 +2340,8 @@ func (a *Association) handleChunk(p *packet, c chunk) error {
return nil
}
isAbort := false
switch c := c.(type) {
case *chunkInit:
packets, err = a.handleInit(p, c)
@ -2045,11 +2350,8 @@ func (a *Association) handleChunk(p *packet, c chunk) error {
err = a.handleInitAck(p, c)
case *chunkAbort:
var errStr string
for _, e := range c.errorCauses {
errStr += fmt.Sprintf("(%s)", e)
}
return fmt.Errorf("[%s] %w: %s", a.name, errChunk, errStr)
isAbort = true
err = a.handleAbort(c)
case *chunkError:
var errStr string
@ -2079,12 +2381,23 @@ func (a *Association) handleChunk(p *packet, c chunk) error {
case *chunkForwardTSN:
packets = a.handleForwardTSN(c)
case *chunkShutdown:
a.handleShutdown(c)
case *chunkShutdownAck:
a.handleShutdownAck(c)
case *chunkShutdownComplete:
err = a.handleShutdownComplete(c)
default:
err = errors.Errorf("unhandled chunk type")
err = ErrChunkTypeUnhandled
}
// Log and return, the only condition that is fatal is a ABORT chunk
if err != nil {
if isAbort {
return err
}
a.log.Errorf("Failed to handle chunk: %v", err)
return nil
}
@ -2117,6 +2430,20 @@ func (a *Association) onRetransmissionTimeout(id int, nRtos uint) {
return
}
if id == timerT2Shutdown {
a.log.Debugf("[%s] retransmission of shutdown timeout (nRtos=%d): %v", a.name, nRtos)
state := a.getState()
switch state {
case shutdownSent:
a.willSendShutdown = true
a.awakeWriteLoop()
case shutdownAckSent:
a.willSendShutdownAck = true
a.awakeWriteLoop()
}
}
if id == timerT3RTX {
a.stats.incT3Timeouts()
@ -2190,13 +2517,18 @@ func (a *Association) onRetransmissionFailure(id int) {
if id == timerT1Init {
a.log.Errorf("[%s] retransmission failure: T1-init", a.name)
a.handshakeCompletedCh <- errors.Errorf("handshake failed (INIT ACK)")
a.handshakeCompletedCh <- ErrHandshakeInitAck
return
}
if id == timerT1Cookie {
a.log.Errorf("[%s] retransmission failure: T1-cookie", a.name)
a.handshakeCompletedCh <- errors.Errorf("handshake failed (COOKIE ECHO)")
a.handshakeCompletedCh <- ErrHandshakeCookieEcho
return
}
if id == timerT2Shutdown {
a.log.Errorf("[%s] retransmission failure: T2-shutdown", a.name)
return
}

View file

@ -1,9 +1,8 @@
package sctp // nolint:dupl
import (
"errors"
"fmt"
"github.com/pkg/errors"
)
/*
@ -17,28 +16,34 @@ SHUTDOWN COMPLETE) MAY be bundled with an ABORT, but they MUST be
placed before the ABORT in the SCTP packet or they will be ignored by
the receiver.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 6 |Reserved |T| Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| zero or more Error Causes |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 6 |Reserved |T| Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| zero or more Error Causes |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type chunkAbort struct {
chunkHeader
errorCauses []errorCause
}
// Abort chunk errors
var (
ErrChunkTypeNotAbort = errors.New("ChunkType is not of type ABORT")
ErrBuildAbortChunkFailed = errors.New("failed build Abort Chunk")
)
func (a *chunkAbort) unmarshal(raw []byte) error {
if err := a.chunkHeader.unmarshal(raw); err != nil {
return err
}
if a.typ != ctAbort {
return errors.Errorf("ChunkType is not of type ABORT, actually is %s", a.typ.String())
return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotAbort, a.typ.String())
}
offset := chunkHeaderSize
@ -49,7 +54,7 @@ func (a *chunkAbort) unmarshal(raw []byte) error {
e, err := buildErrorCause(raw[offset:])
if err != nil {
return errors.Wrap(err, "Failed build Abort Chunk")
return fmt.Errorf("%w: %v", ErrBuildAbortChunkFailed, err) //nolint:errorlint
}
offset += int(e.length())

View file

@ -1,29 +1,35 @@
package sctp
import (
"github.com/pkg/errors"
"errors"
"fmt"
)
/*
chunkCookieAck represents an SCTP Chunk of type chunkCookieAck
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 11 |Chunk Flags | Length = 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 11 |Chunk Flags | Length = 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type chunkCookieAck struct {
chunkHeader
}
// Cookie ack chunk errors
var (
ErrChunkTypeNotCookieAck = errors.New("ChunkType is not of type COOKIEACK")
)
func (c *chunkCookieAck) unmarshal(raw []byte) error {
if err := c.chunkHeader.unmarshal(raw); err != nil {
return err
}
if c.typ != ctCookieAck {
return errors.Errorf("ChunkType is not of type COOKIEACK, actually is %s", c.typ.String())
return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotCookieAck, c.typ.String())
}
return nil

View file

@ -1,34 +1,39 @@
package sctp
import (
"github.com/pkg/errors"
"errors"
"fmt"
)
/*
CookieEcho represents an SCTP Chunk of type CookieEcho
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 10 |Chunk Flags | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cookie |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 10 |Chunk Flags | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cookie |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type chunkCookieEcho struct {
chunkHeader
cookie []byte
}
// Cookie echo chunk errors
var (
ErrChunkTypeNotCookieEcho = errors.New("ChunkType is not of type COOKIEECHO")
)
func (c *chunkCookieEcho) unmarshal(raw []byte) error {
if err := c.chunkHeader.unmarshal(raw); err != nil {
return err
}
if c.typ != ctCookieEcho {
return errors.Errorf("ChunkType is not of type COOKIEECHO, actually is %s", c.typ.String())
return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotCookieEcho, c.typ.String())
}
c.cookie = c.raw

View file

@ -1,51 +1,56 @@
package sctp // nolint:dupl
import (
"errors"
"fmt"
"github.com/pkg/errors"
)
/*
Operation Error (ERROR) (9)
Operation Error (ERROR) (9)
An endpoint sends this chunk to its peer endpoint to notify it of
certain error conditions. It contains one or more error causes. An
Operation Error is not considered fatal in and of itself, but may be
used with an ERROR chunk to report a fatal condition. It has the
following parameters:
An endpoint sends this chunk to its peer endpoint to notify it of
certain error conditions. It contains one or more error causes. An
Operation Error is not considered fatal in and of itself, but may be
used with an ERROR chunk to report a fatal condition. It has the
following parameters:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 9 | Chunk Flags | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\ \
/ one or more Error Causes /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 9 | Chunk Flags | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\ \
/ one or more Error Causes /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk Flags: 8 bits
Chunk Flags: 8 bits
Set to 0 on transmit and ignored on receipt.
Set to 0 on transmit and ignored on receipt.
Length: 16 bits (unsigned integer)
Length: 16 bits (unsigned integer)
Set to the size of the chunk in bytes, including the chunk header
and all the Error Cause fields present.
Set to the size of the chunk in bytes, including the chunk header
and all the Error Cause fields present.
*/
type chunkError struct {
chunkHeader
errorCauses []errorCause
}
// Error chunk errors
var (
ErrChunkTypeNotCtError = errors.New("ChunkType is not of type ctError")
ErrBuildErrorChunkFailed = errors.New("failed build Error Chunk")
)
func (a *chunkError) unmarshal(raw []byte) error {
if err := a.chunkHeader.unmarshal(raw); err != nil {
return err
}
if a.typ != ctError {
return errors.Errorf("ChunkType is not of type ctError, actually is %s", a.typ.String())
return fmt.Errorf("%w, actually is %s", ErrChunkTypeNotCtError, a.typ.String())
}
offset := chunkHeaderSize
@ -56,7 +61,7 @@ func (a *chunkError) unmarshal(raw []byte) error {
e, err := buildErrorCause(raw[offset:])
if err != nil {
return errors.Wrap(err, "Failed build Error Chunk")
return fmt.Errorf("%w: %v", ErrBuildErrorChunkFailed, err) //nolint:errorlint
}
offset += int(e.length())

View file

@ -2,9 +2,8 @@ package sctp
import (
"encoding/binary"
"errors"
"fmt"
"github.com/pkg/errors"
)
// This chunk shall be used by the data sender to inform the data
@ -44,7 +43,11 @@ const (
forwardTSNStreamLength = 4
)
var errMarshalStreamFailed = errors.New("failed to marshal stream")
// Forward TSN chunk errors
var (
ErrMarshalStreamFailed = errors.New("failed to marshal stream")
ErrChunkTooShort = errors.New("chunk too short")
)
func (c *chunkForwardTSN) unmarshal(raw []byte) error {
if err := c.chunkHeader.unmarshal(raw); err != nil {
@ -52,7 +55,7 @@ func (c *chunkForwardTSN) unmarshal(raw []byte) error {
}
if len(c.raw) < newCumulativeTSNLength {
return errors.New("chunk to short")
return ErrChunkTooShort
}
c.newCumulativeTSN = binary.BigEndian.Uint32(c.raw[0:])
@ -63,7 +66,7 @@ func (c *chunkForwardTSN) unmarshal(raw []byte) error {
s := chunkForwardTSNStream{}
if err := s.unmarshal(c.raw[offset:]); err != nil {
return fmt.Errorf("failed to unmarshal stream: %w", err)
return fmt.Errorf("%w: %v", ErrMarshalStreamFailed, err) //nolint:errorlint
}
c.streams = append(c.streams, s)
@ -82,7 +85,7 @@ func (c *chunkForwardTSN) marshal() ([]byte, error) {
for _, s := range c.streams {
b, err := s.marshal()
if err != nil {
return nil, fmt.Errorf("%w: %v", errMarshalStreamFailed, err)
return nil, fmt.Errorf("%w: %v", ErrMarshalStreamFailed, err) //nolint:errorlint
}
out = append(out, b...)
}
@ -127,7 +130,7 @@ func (s *chunkForwardTSNStream) length() int {
func (s *chunkForwardTSNStream) unmarshal(raw []byte) error {
if len(raw) < forwardTSNStreamLength {
return errors.New("stream to short")
return ErrChunkTooShort
}
s.identifier = binary.BigEndian.Uint16(raw[0:])
s.sequence = binary.BigEndian.Uint16(raw[2:])

View file

@ -1,7 +1,8 @@
package sctp
import (
"github.com/pkg/errors"
"errors"
"fmt"
)
/*
@ -14,16 +15,15 @@ the present association.
The parameter field contains the Heartbeat Information, which is a
variable-length opaque data structure understood only by the sender.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 4 | Chunk Flags | Heartbeat Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Heartbeat Information TLV (Variable-Length) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 4 | Chunk Flags | Heartbeat Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Heartbeat Information TLV (Variable-Length) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Defined as a variable-length parameter using the format described
in Section 3.2.1, i.e.:
@ -31,35 +31,43 @@ in Section 3.2.1, i.e.:
Variable Parameters Status Type Value
-------------------------------------------------------------
heartbeat Info Mandatory 1
*/
type chunkHeartbeat struct {
chunkHeader
params []param
}
// Heartbeat chunk errors
var (
ErrChunkTypeNotHeartbeat = errors.New("ChunkType is not of type HEARTBEAT")
ErrHeartbeatNotLongEnoughInfo = errors.New("heartbeat is not long enough to contain Heartbeat Info")
ErrParseParamTypeFailed = errors.New("failed to parse param type")
ErrHeartbeatParam = errors.New("heartbeat should only have HEARTBEAT param")
ErrHeartbeatChunkUnmarshal = errors.New("failed unmarshalling param in Heartbeat Chunk")
)
func (h *chunkHeartbeat) unmarshal(raw []byte) error {
if err := h.chunkHeader.unmarshal(raw); err != nil {
return err
} else if h.typ != ctHeartbeat {
return errors.Errorf("ChunkType is not of type HEARTBEAT, actually is %s", h.typ.String())
return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotHeartbeat, h.typ.String())
}
if len(raw) <= chunkHeaderSize {
return errors.Errorf("Heartbeat is not long enough to contain Heartbeat Info %d", len(raw))
return fmt.Errorf("%w: %d", ErrHeartbeatNotLongEnoughInfo, len(raw))
}
pType, err := parseParamType(raw[chunkHeaderSize:])
if err != nil {
return errors.Wrap(err, "failed to parse param type")
return fmt.Errorf("%w: %v", ErrParseParamTypeFailed, err) //nolint:errorlint
}
if pType != heartbeatInfo {
return errors.Errorf("Heartbeat should only have HEARTBEAT param, instead have %s", pType.String())
return fmt.Errorf("%w: instead have %s", ErrHeartbeatParam, pType.String())
}
p, err := buildParam(pType, raw[chunkHeaderSize:])
if err != nil {
return errors.Wrap(err, "Failed unmarshalling param in Heartbeat Chunk")
return fmt.Errorf("%w: %v", ErrHeartbeatChunkUnmarshal, err) //nolint:errorlint
}
h.params = append(h.params, p)
@ -67,7 +75,7 @@ func (h *chunkHeartbeat) unmarshal(raw []byte) error {
}
func (h *chunkHeartbeat) Marshal() ([]byte, error) {
return nil, errors.Errorf("Unimplemented")
return nil, ErrUnimplemented
}
func (h *chunkHeartbeat) check() (abort bool, err error) {

View file

@ -1,7 +1,8 @@
package sctp
import (
"github.com/pkg/errors"
"errors"
"fmt"
)
/*
@ -14,16 +15,15 @@ HEARTBEAT chunk to which this ack is responding.
The parameter field contains a variable-length opaque data structure.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 5 | Chunk Flags | Heartbeat Ack Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Heartbeat Information TLV (Variable-Length) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 5 | Chunk Flags | Heartbeat Ack Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Heartbeat Information TLV (Variable-Length) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Defined as a variable-length parameter using the format described
in Section 3.2.1, i.e.:
@ -31,34 +31,41 @@ in Section 3.2.1, i.e.:
Variable Parameters Status Type Value
-------------------------------------------------------------
Heartbeat Info Mandatory 1
*/
type chunkHeartbeatAck struct {
chunkHeader
params []param
}
func (h *chunkHeartbeatAck) unmarshal(raw []byte) error {
return errors.Errorf("Unimplemented")
// Heartbeat ack chunk errors
var (
ErrUnimplemented = errors.New("unimplemented")
ErrHeartbeatAckParams = errors.New("heartbeat Ack must have one param")
ErrHeartbeatAckNotHeartbeatInfo = errors.New("heartbeat Ack must have one param, and it should be a HeartbeatInfo")
ErrHeartbeatAckMarshalParam = errors.New("unable to marshal parameter for Heartbeat Ack")
)
func (h *chunkHeartbeatAck) unmarshal([]byte) error {
return ErrUnimplemented
}
func (h *chunkHeartbeatAck) marshal() ([]byte, error) {
if len(h.params) != 1 {
return nil, errors.Errorf("Heartbeat Ack must have one param")
return nil, ErrHeartbeatAckParams
}
switch h.params[0].(type) {
case *paramHeartbeatInfo:
// ParamHeartbeatInfo is valid
default:
return nil, errors.Errorf("Heartbeat Ack must have one param, and it should be a HeartbeatInfo")
return nil, ErrHeartbeatAckNotHeartbeatInfo
}
out := make([]byte, 0)
for idx, p := range h.params {
pp, err := p.marshal()
if err != nil {
return nil, errors.Wrap(err, "Unable to marshal parameter for Heartbeat Ack")
return nil, fmt.Errorf("%w: %v", ErrHeartbeatAckMarshalParam, err) //nolint:errorlint
}
out = append(out, pp...)

View file

@ -1,9 +1,8 @@
package sctp // nolint:dupl
import (
"errors"
"fmt"
"github.com/pkg/errors"
)
/*
@ -11,40 +10,53 @@ Init represents an SCTP Chunk of type INIT
See chunkInitCommon for the fixed headers
Variable Parameters Status Type Value
-------------------------------------------------------------
IPv4 IP (Note 1) Optional 5
IPv6 IP (Note 1) Optional 6
Cookie Preservative Optional 9
Reserved for ECN Capable (Note 2) Optional 32768 (0x8000)
Host Name IP (Note 3) Optional 11
Supported IP Types (Note 4) Optional 12
Variable Parameters Status Type Value
-------------------------------------------------------------
IPv4 IP (Note 1) Optional 5
IPv6 IP (Note 1) Optional 6
Cookie Preservative Optional 9
Reserved for ECN Capable (Note 2) Optional 32768 (0x8000)
Host Name IP (Note 3) Optional 11
Supported IP Types (Note 4) Optional 12
*/
type chunkInit struct {
chunkHeader
chunkInitCommon
}
// Init chunk errors
var (
ErrChunkTypeNotTypeInit = errors.New("ChunkType is not of type INIT")
ErrChunkValueNotLongEnough = errors.New("chunk Value isn't long enough for mandatory parameters exp")
ErrChunkTypeInitFlagZero = errors.New("ChunkType of type INIT flags must be all 0")
ErrChunkTypeInitUnmarshalFailed = errors.New("failed to unmarshal INIT body")
ErrChunkTypeInitMarshalFailed = errors.New("failed marshaling INIT common data")
ErrChunkTypeInitInitateTagZero = errors.New("ChunkType of type INIT ACK InitiateTag must not be 0")
ErrInitInboundStreamRequestZero = errors.New("INIT ACK inbound stream request must be > 0")
ErrInitOutboundStreamRequestZero = errors.New("INIT ACK outbound stream request must be > 0")
ErrInitAdvertisedReceiver1500 = errors.New("INIT ACK Advertised Receiver Window Credit (a_rwnd) must be >= 1500")
)
func (i *chunkInit) unmarshal(raw []byte) error {
if err := i.chunkHeader.unmarshal(raw); err != nil {
return err
}
if i.typ != ctInit {
return errors.Errorf("ChunkType is not of type INIT, actually is %s", i.typ.String())
return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotTypeInit, i.typ.String())
} else if len(i.raw) < initChunkMinLength {
return errors.Errorf("Chunk Value isn't long enough for mandatory parameters exp: %d actual: %d", initChunkMinLength, len(i.raw))
return fmt.Errorf("%w: %d actual: %d", ErrChunkValueNotLongEnough, initChunkMinLength, len(i.raw))
}
// The Chunk Flags field in INIT is reserved, and all bits in it should
// be set to 0 by the sender and ignored by the receiver. The sequence
// of parameters within an INIT can be processed in any order.
if i.flags != 0 {
return errors.New("ChunkType of type INIT flags must be all 0")
return ErrChunkTypeInitFlagZero
}
if err := i.chunkInitCommon.unmarshal(i.raw); err != nil {
return errors.Wrap(err, "Failed to unmarshal INIT body")
return fmt.Errorf("%w: %v", ErrChunkTypeInitUnmarshalFailed, err) //nolint:errorlint
}
return nil
@ -53,7 +65,7 @@ func (i *chunkInit) unmarshal(raw []byte) error {
func (i *chunkInit) marshal() ([]byte, error) {
initShared, err := i.chunkInitCommon.marshal()
if err != nil {
return nil, errors.Wrap(err, "Failed marshaling INIT common data")
return nil, fmt.Errorf("%w: %v", ErrChunkTypeInitMarshalFailed, err) //nolint:errorlint
}
i.chunkHeader.typ = ctInit
@ -75,7 +87,7 @@ func (i *chunkInit) check() (abort bool, err error) {
// association by transmitting an ABORT.
if i.initiateTag == 0 {
abort = true
return abort, errors.New("ChunkType of type INIT InitiateTag must not be 0")
return abort, ErrChunkTypeInitInitateTagZero
}
// Defines the maximum number of streams the sender of this INIT
@ -90,7 +102,7 @@ func (i *chunkInit) check() (abort bool, err error) {
// the association.
if i.numInboundStreams == 0 {
abort = true
return abort, errors.New("INIT inbound stream request must be > 0")
return abort, ErrInitInboundStreamRequestZero
}
// Defines the number of outbound streams the sender of this INIT
@ -102,7 +114,7 @@ func (i *chunkInit) check() (abort bool, err error) {
if i.numOutboundStreams == 0 {
abort = true
return abort, errors.New("INIT outbound stream request must be > 0")
return abort, ErrInitOutboundStreamRequestZero
}
// An SCTP receiver MUST be able to receive a minimum of 1500 bytes in
@ -111,7 +123,7 @@ func (i *chunkInit) check() (abort bool, err error) {
// ACK.
if i.advertisedReceiverWindowCredit < 1500 {
abort = true
return abort, errors.New("INIT Advertised Receiver Window Credit (a_rwnd) must be >= 1500")
return abort, ErrInitAdvertisedReceiver1500
}
return false, nil

View file

@ -1,9 +1,8 @@
package sctp // nolint:dupl
import (
"errors"
"fmt"
"github.com/pkg/errors"
)
/*
@ -11,41 +10,53 @@ chunkInitAck represents an SCTP Chunk of type INIT ACK
See chunkInitCommon for the fixed headers
Variable Parameters Status Type Value
-------------------------------------------------------------
State Cookie Mandatory 7
IPv4 IP (Note 1) Optional 5
IPv6 IP (Note 1) Optional 6
Unrecognized Parameter Optional 8
Reserved for ECN Capable (Note 2) Optional 32768 (0x8000)
Host Name IP (Note 3) Optional 11<Paste>
Variable Parameters Status Type Value
-------------------------------------------------------------
State Cookie Mandatory 7
IPv4 IP (Note 1) Optional 5
IPv6 IP (Note 1) Optional 6
Unrecognized Parameter Optional 8
Reserved for ECN Capable (Note 2) Optional 32768 (0x8000)
Host Name IP (Note 3) Optional 11<Paste>
*/
type chunkInitAck struct {
chunkHeader
chunkInitCommon
}
// Init ack chunk errors
var (
ErrChunkTypeNotInitAck = errors.New("ChunkType is not of type INIT ACK")
ErrChunkNotLongEnoughForParams = errors.New("chunk Value isn't long enough for mandatory parameters exp")
ErrChunkTypeInitAckFlagZero = errors.New("ChunkType of type INIT ACK flags must be all 0")
ErrInitAckUnmarshalFailed = errors.New("failed to unmarshal INIT body")
ErrInitCommonDataMarshalFailed = errors.New("failed marshaling INIT common data")
ErrChunkTypeInitAckInitateTagZero = errors.New("ChunkType of type INIT ACK InitiateTag must not be 0")
ErrInitAckInboundStreamRequestZero = errors.New("INIT ACK inbound stream request must be > 0")
ErrInitAckOutboundStreamRequestZero = errors.New("INIT ACK outbound stream request must be > 0")
ErrInitAckAdvertisedReceiver1500 = errors.New("INIT ACK Advertised Receiver Window Credit (a_rwnd) must be >= 1500")
)
func (i *chunkInitAck) unmarshal(raw []byte) error {
if err := i.chunkHeader.unmarshal(raw); err != nil {
return err
}
if i.typ != ctInitAck {
return errors.Errorf("ChunkType is not of type INIT ACK, actually is %s", i.typ.String())
return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotInitAck, i.typ.String())
} else if len(i.raw) < initChunkMinLength {
return errors.Errorf("Chunk Value isn't long enough for mandatory parameters exp: %d actual: %d", initChunkMinLength, len(i.raw))
return fmt.Errorf("%w: %d actual: %d", ErrChunkNotLongEnoughForParams, initChunkMinLength, len(i.raw))
}
// The Chunk Flags field in INIT is reserved, and all bits in it should
// be set to 0 by the sender and ignored by the receiver. The sequence
// of parameters within an INIT can be processed in any order.
if i.flags != 0 {
return errors.New("ChunkType of type INIT ACK flags must be all 0")
return ErrChunkTypeInitAckFlagZero
}
if err := i.chunkInitCommon.unmarshal(i.raw); err != nil {
return errors.Wrap(err, "Failed to unmarshal INIT body")
return fmt.Errorf("%w: %v", ErrInitAckUnmarshalFailed, err) //nolint:errorlint
}
return nil
@ -54,7 +65,7 @@ func (i *chunkInitAck) unmarshal(raw []byte) error {
func (i *chunkInitAck) marshal() ([]byte, error) {
initShared, err := i.chunkInitCommon.marshal()
if err != nil {
return nil, errors.Wrap(err, "Failed marshaling INIT common data")
return nil, fmt.Errorf("%w: %v", ErrInitCommonDataMarshalFailed, err) //nolint:errorlint
}
i.chunkHeader.typ = ctInitAck
@ -77,7 +88,7 @@ func (i *chunkInitAck) check() (abort bool, err error) {
// purpose.
if i.initiateTag == 0 {
abort = true
return abort, errors.New("ChunkType of type INIT ACK InitiateTag must not be 0")
return abort, ErrChunkTypeInitAckInitateTagZero
}
// Defines the maximum number of streams the sender of this INIT ACK
@ -92,7 +103,7 @@ func (i *chunkInitAck) check() (abort bool, err error) {
// destroy the association discarding its TCB.
if i.numInboundStreams == 0 {
abort = true
return abort, errors.New("INIT ACK inbound stream request must be > 0")
return abort, ErrInitAckInboundStreamRequestZero
}
// Defines the number of outbound streams the sender of this INIT ACK
@ -105,7 +116,7 @@ func (i *chunkInitAck) check() (abort bool, err error) {
if i.numOutboundStreams == 0 {
abort = true
return abort, errors.New("INIT ACK outbound stream request must be > 0")
return abort, ErrInitAckOutboundStreamRequestZero
}
// An SCTP receiver MUST be able to receive a minimum of 1500 bytes in
@ -114,7 +125,7 @@ func (i *chunkInitAck) check() (abort bool, err error) {
// ACK.
if i.advertisedReceiverWindowCredit < 1500 {
abort = true
return abort, errors.New("INIT ACK Advertised Receiver Window Credit (a_rwnd) must be >= 1500")
return abort, ErrInitAckAdvertisedReceiver1500
}
return false, nil

View file

@ -2,9 +2,8 @@ package sctp
import (
"encoding/binary"
"errors"
"fmt"
"github.com/pkg/errors"
)
/*
@ -54,6 +53,13 @@ const (
initOptionalVarHeaderLength = 4
)
// Init chunk errors
var (
ErrInitChunkParseParamTypeFailed = errors.New("failed to parse param type")
ErrInitChunkUnmarshalParam = errors.New("failed unmarshalling param in Init Chunk")
ErrInitAckMarshalParam = errors.New("unable to marshal parameter for INIT/INITACK")
)
func (i *chunkInitCommon) unmarshal(raw []byte) error {
i.initiateTag = binary.BigEndian.Uint32(raw[0:])
i.advertisedReceiverWindowCredit = binary.BigEndian.Uint32(raw[4:])
@ -84,11 +90,11 @@ func (i *chunkInitCommon) unmarshal(raw []byte) error {
if remaining > initOptionalVarHeaderLength {
pType, err := parseParamType(raw[offset:])
if err != nil {
return errors.Wrap(err, "failed to parse param type")
return fmt.Errorf("%w: %v", ErrInitChunkParseParamTypeFailed, err) //nolint:errorlint
}
p, err := buildParam(pType, raw[offset:])
if err != nil {
return errors.Wrap(err, "Failed unmarshalling param in Init Chunk")
return fmt.Errorf("%w: %v", ErrInitChunkUnmarshalParam, err) //nolint:errorlint
}
i.params = append(i.params, p)
padding := getPadding(p.length())
@ -112,7 +118,7 @@ func (i *chunkInitCommon) marshal() ([]byte, error) {
for idx, p := range i.params {
pp, err := p.marshal()
if err != nil {
return nil, errors.Wrap(err, "Unable to marshal parameter for INIT/INITACK")
return nil, fmt.Errorf("%w: %v", ErrInitAckMarshalParam, err) //nolint:errorlint
}
out = append(out, pp...)

View file

@ -2,6 +2,7 @@ package sctp
import (
"encoding/binary"
"errors"
"fmt"
"time"
)
@ -9,38 +10,38 @@ import (
/*
chunkPayloadData represents an SCTP Chunk of type DATA
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 0 | Reserved|U|B|E| Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| TSN |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Stream Identifier S | Stream Sequence Number n |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Protocol Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| User Data (seq n of Stream S) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 0 | Reserved|U|B|E| Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| TSN |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Stream Identifier S | Stream Sequence Number n |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Protocol Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| User Data (seq n of Stream S) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
An unfragmented user message shall have both the B and E bits set to
'1'. Setting both B and E bits to '0' indicates a middle fragment of
a multi-fragment user message, as summarized in the following table:
B E Description
============================================================
| 1 0 | First piece of a fragmented user message |
+----------------------------------------------------------+
| 0 0 | Middle piece of a fragmented user message |
+----------------------------------------------------------+
| 0 1 | Last piece of a fragmented user message |
+----------------------------------------------------------+
| 1 1 | Unfragmented message |
============================================================
| Table 1: Fragment Description Flags |
============================================================
B E Description
============================================================
| 1 0 | First piece of a fragmented user message |
+----------------------------------------------------------+
| 0 0 | Middle piece of a fragmented user message |
+----------------------------------------------------------+
| 0 1 | Last piece of a fragmented user message |
+----------------------------------------------------------+
| 1 1 | Unfragmented message |
============================================================
| Table 1: Fragment Description Flags |
============================================================
*/
type chunkPayloadData struct {
chunkHeader
@ -88,6 +89,7 @@ type PayloadProtocolIdentifier uint32
// PayloadProtocolIdentifier enums
// https://www.iana.org/assignments/sctp-parameters/sctp-parameters.xhtml#sctp-parameters-25
const (
PayloadTypeUnknown PayloadProtocolIdentifier = 0
PayloadTypeWebRTCDCEP PayloadProtocolIdentifier = 50
PayloadTypeWebRTCString PayloadProtocolIdentifier = 51
PayloadTypeWebRTCBinary PayloadProtocolIdentifier = 53
@ -95,6 +97,11 @@ const (
PayloadTypeWebRTCBinaryEmpty PayloadProtocolIdentifier = 57
)
// Data chunk errors
var (
ErrChunkPayloadSmall = errors.New("packet is smaller than the header size")
)
func (p PayloadProtocolIdentifier) String() string {
switch p {
case PayloadTypeWebRTCDCEP:
@ -122,6 +129,9 @@ func (p *chunkPayloadData) unmarshal(raw []byte) error {
p.beginningFragment = p.flags&payloadDataBeginingFragmentBitmask != 0
p.endingFragment = p.flags&payloadDataEndingFragmentBitmask != 0
if len(raw) < payloadDataHeaderSize {
return ErrChunkPayloadSmall
}
p.tsn = binary.BigEndian.Uint32(p.raw[0:])
p.streamIdentifier = binary.BigEndian.Uint16(p.raw[4:])
p.streamSequenceNumber = binary.BigEndian.Uint16(p.raw[6:])

View file

@ -1,9 +1,8 @@
package sctp
import (
"errors"
"fmt"
"github.com/pkg/errors"
)
// https://tools.ietf.org/html/rfc6525#section-3.1
@ -29,13 +28,20 @@ type chunkReconfig struct {
paramB param
}
// Reconfigure chunk errors
var (
ErrChunkParseParamTypeFailed = errors.New("failed to parse param type")
ErrChunkMarshalParamAReconfigFailed = errors.New("unable to marshal parameter A for reconfig")
ErrChunkMarshalParamBReconfigFailed = errors.New("unable to marshal parameter B for reconfig")
)
func (c *chunkReconfig) unmarshal(raw []byte) error {
if err := c.chunkHeader.unmarshal(raw); err != nil {
return err
}
pType, err := parseParamType(c.raw)
if err != nil {
return errors.Wrap(err, "failed to parse param type")
return fmt.Errorf("%w: %v", ErrChunkParseParamTypeFailed, err) //nolint:errorlint
}
a, err := buildParam(pType, c.raw)
if err != nil {
@ -48,7 +54,7 @@ func (c *chunkReconfig) unmarshal(raw []byte) error {
if len(c.raw) > offset {
pType, err := parseParamType(c.raw[offset:])
if err != nil {
return errors.Wrap(err, "failed to parse param type")
return fmt.Errorf("%w: %v", ErrChunkParseParamTypeFailed, err) //nolint:errorlint
}
b, err := buildParam(pType, c.raw[offset:])
if err != nil {
@ -63,7 +69,7 @@ func (c *chunkReconfig) unmarshal(raw []byte) error {
func (c *chunkReconfig) marshal() ([]byte, error) {
out, err := c.paramA.marshal()
if err != nil {
return nil, errors.Wrap(err, "Unable to marshal parameter A for reconfig")
return nil, fmt.Errorf("%w: %v", ErrChunkMarshalParamAReconfigFailed, err) //nolint:errorlint
}
if c.paramB != nil {
// Pad param A
@ -71,7 +77,7 @@ func (c *chunkReconfig) marshal() ([]byte, error) {
outB, err := c.paramB.marshal()
if err != nil {
return nil, errors.Wrap(err, "Unable to marshal parameter B for reconfig")
return nil, fmt.Errorf("%w: %v", ErrChunkMarshalParamBReconfigFailed, err) //nolint:errorlint
}
out = append(out, outB...)

View file

@ -2,9 +2,8 @@ package sctp
import (
"encoding/binary"
"errors"
"fmt"
"github.com/pkg/errors"
)
/*
@ -47,6 +46,13 @@ type gapAckBlock struct {
end uint16
}
// Selective ack chunk errors
var (
ErrChunkTypeNotSack = errors.New("ChunkType is not of type SACK")
ErrSackSizeNotLargeEnoughInfo = errors.New("SACK Chunk size is not large enough to contain header")
ErrSackSizeNotMatchPredicted = errors.New("SACK Chunk size does not match predicted amount from header values")
)
// String makes gapAckBlock printable
func (g gapAckBlock) String() string {
return fmt.Sprintf("%d - %d", g.start, g.end)
@ -70,11 +76,11 @@ func (s *chunkSelectiveAck) unmarshal(raw []byte) error {
}
if s.typ != ctSack {
return errors.Errorf("ChunkType is not of type SACK, actually is %s", s.typ.String())
return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotSack, s.typ.String())
}
if len(s.raw) < selectiveAckHeaderSize {
return errors.Errorf("SACK Chunk size is not large enough to contain header (%v remaining, needs %v bytes)",
return fmt.Errorf("%w: %v remaining, needs %v bytes", ErrSackSizeNotLargeEnoughInfo,
len(s.raw), selectiveAckHeaderSize)
}
@ -84,7 +90,7 @@ func (s *chunkSelectiveAck) unmarshal(raw []byte) error {
s.duplicateTSN = make([]uint32, binary.BigEndian.Uint16(s.raw[10:]))
if len(s.raw) != selectiveAckHeaderSize+(4*len(s.gapAckBlocks)+(4*len(s.duplicateTSN))) {
return errors.Errorf("SACK Chunk size does not match predicted amount from header values")
return ErrSackSizeNotMatchPredicted
}
offset := selectiveAckHeaderSize

View file

@ -0,0 +1,69 @@
package sctp
import (
"encoding/binary"
"errors"
"fmt"
)
/*
chunkShutdown represents an SCTP Chunk of type chunkShutdown
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 7 | Chunk Flags | Length = 8 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cumulative TSN Ack |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type chunkShutdown struct {
chunkHeader
cumulativeTSNAck uint32
}
const (
cumulativeTSNAckLength = 4
)
// Shutdown chunk errors
var (
ErrInvalidChunkSize = errors.New("invalid chunk size")
ErrChunkTypeNotShutdown = errors.New("ChunkType is not of type SHUTDOWN")
)
func (c *chunkShutdown) unmarshal(raw []byte) error {
if err := c.chunkHeader.unmarshal(raw); err != nil {
return err
}
if c.typ != ctShutdown {
return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotShutdown, c.typ.String())
}
if len(c.raw) != cumulativeTSNAckLength {
return ErrInvalidChunkSize
}
c.cumulativeTSNAck = binary.BigEndian.Uint32(c.raw[0:])
return nil
}
func (c *chunkShutdown) marshal() ([]byte, error) {
out := make([]byte, cumulativeTSNAckLength)
binary.BigEndian.PutUint32(out[0:], c.cumulativeTSNAck)
c.typ = ctShutdown
c.raw = out
return c.chunkHeader.marshal()
}
func (c *chunkShutdown) check() (abort bool, err error) {
return false, nil
}
// String makes chunkShutdown printable
func (c *chunkShutdown) String() string {
return c.chunkHeader.String()
}

View file

@ -0,0 +1,50 @@
package sctp
import (
"errors"
"fmt"
)
/*
chunkShutdownAck represents an SCTP Chunk of type chunkShutdownAck
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 8 | Chunk Flags | Length = 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type chunkShutdownAck struct {
chunkHeader
}
// Shutdown ack chunk errors
var (
ErrChunkTypeNotShutdownAck = errors.New("ChunkType is not of type SHUTDOWN-ACK")
)
func (c *chunkShutdownAck) unmarshal(raw []byte) error {
if err := c.chunkHeader.unmarshal(raw); err != nil {
return err
}
if c.typ != ctShutdownAck {
return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotShutdownAck, c.typ.String())
}
return nil
}
func (c *chunkShutdownAck) marshal() ([]byte, error) {
c.typ = ctShutdownAck
return c.chunkHeader.marshal()
}
func (c *chunkShutdownAck) check() (abort bool, err error) {
return false, nil
}
// String makes chunkShutdownAck printable
func (c *chunkShutdownAck) String() string {
return c.chunkHeader.String()
}

View file

@ -0,0 +1,50 @@
package sctp
import (
"errors"
"fmt"
)
/*
chunkShutdownComplete represents an SCTP Chunk of type chunkShutdownComplete
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 14 |Reserved |T| Length = 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type chunkShutdownComplete struct {
chunkHeader
}
// Shutdown complete chunk errors
var (
ErrChunkTypeNotShutdownComplete = errors.New("ChunkType is not of type SHUTDOWN-COMPLETE")
)
func (c *chunkShutdownComplete) unmarshal(raw []byte) error {
if err := c.chunkHeader.unmarshal(raw); err != nil {
return err
}
if c.typ != ctShutdownComplete {
return fmt.Errorf("%w: actually is %s", ErrChunkTypeNotShutdownComplete, c.typ.String())
}
return nil
}
func (c *chunkShutdownComplete) marshal() ([]byte, error) {
c.typ = ctShutdownComplete
return c.chunkHeader.marshal()
}
func (c *chunkShutdownComplete) check() (abort bool, err error) {
return false, nil
}
// String makes chunkShutdownComplete printable
func (c *chunkShutdownComplete) String() string {
return c.chunkHeader.String()
}

View file

@ -2,8 +2,8 @@ package sctp
import (
"encoding/binary"
"github.com/pkg/errors"
"errors"
"fmt"
)
/*
@ -13,15 +13,15 @@ transmitted in the SCTP packet. Each chunk is formatted with a Chunk
Type field, a chunk-specific Flag field, a Chunk Length field, and a
Value field.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Chunk Type | Chunk Flags | Chunk Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Chunk Value |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Chunk Type | Chunk Flags | Chunk Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Chunk Value |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type chunkHeader struct {
typ chunkType
@ -33,9 +33,16 @@ const (
chunkHeaderSize = 4
)
// SCTP chunk header errors
var (
ErrChunkHeaderTooSmall = errors.New("raw is too small for a SCTP chunk")
ErrChunkHeaderNotEnoughSpace = errors.New("not enough data left in SCTP packet to satisfy requested length")
ErrChunkHeaderPaddingNonZero = errors.New("chunk padding is non-zero at offset")
)
func (c *chunkHeader) unmarshal(raw []byte) error {
if len(raw) < chunkHeaderSize {
return errors.Errorf("raw only %d bytes, %d is the minimum length for a SCTP chunk", len(raw), chunkHeaderSize)
return fmt.Errorf("%w: raw only %d bytes, %d is the minimum length", ErrChunkHeaderTooSmall, len(raw), chunkHeaderSize)
}
c.typ = chunkType(raw[0])
@ -47,7 +54,7 @@ func (c *chunkHeader) unmarshal(raw []byte) error {
lengthAfterValue := len(raw) - (chunkHeaderSize + valueLength)
if lengthAfterValue < 0 {
return errors.Errorf("Not enough data left in SCTP packet to satisfy requested length remain %d req %d ", valueLength, len(raw)-chunkHeaderSize)
return fmt.Errorf("%w: remain %d req %d ", ErrChunkHeaderNotEnoughSpace, valueLength, len(raw)-chunkHeaderSize)
} else if lengthAfterValue < 4 {
// https://tools.ietf.org/html/rfc4960#section-3.2
// The Chunk Length field does not count any chunk padding.
@ -61,7 +68,7 @@ func (c *chunkHeader) unmarshal(raw []byte) error {
for i := lengthAfterValue; i > 0; i-- {
paddingOffset := chunkHeaderSize + valueLength + (i - 1)
if raw[paddingOffset] != 0 {
return errors.Errorf("Chunk padding is non-zero at offset %d ", paddingOffset)
return fmt.Errorf("%w: %d ", ErrChunkHeaderPaddingNonZero, paddingOffset)
}
}
}

View file

@ -2,9 +2,8 @@ package sctp
import (
"encoding/binary"
"errors"
"fmt"
"github.com/pkg/errors"
)
// errorCauseCode is a cause code that appears in either a ERROR or ABORT chunk
@ -19,6 +18,11 @@ type errorCause interface {
errorCauseCode() errorCauseCode
}
// Error and abort chunk errors
var (
ErrBuildErrorCaseHandle = errors.New("BuildErrorCause does not handle")
)
// buildErrorCause delegates the building of a error cause from raw bytes to the correct structure
func buildErrorCause(raw []byte) (errorCause, error) {
var e errorCause
@ -31,13 +35,16 @@ func buildErrorCause(raw []byte) (errorCause, error) {
e = &errorCauseUnrecognizedChunkType{}
case protocolViolation:
e = &errorCauseProtocolViolation{}
case userInitiatedAbort:
e = &errorCauseUserInitiatedAbort{}
default:
return nil, errors.Errorf("BuildErrorCause does not handle %s", c.String())
return nil, fmt.Errorf("%w: %s", ErrBuildErrorCaseHandle, c.String())
}
if err := e.unmarshal(raw); err != nil {
return nil, err
}
return e, nil
}

View file

@ -1,33 +1,37 @@
package sctp
import (
"errors"
"fmt"
"github.com/pkg/errors"
)
/*
This error cause MAY be included in ABORT chunks that are sent
because an SCTP endpoint detects a protocol violation of the peer
that is not covered by the error causes described in Section 3.3.10.1
to Section 3.3.10.12. An implementation MAY provide additional
information specifying what kind of protocol violation has been
detected.
This error cause MAY be included in ABORT chunks that are sent
because an SCTP endpoint detects a protocol violation of the peer
that is not covered by the error causes described in Section 3.3.10.1
to Section 3.3.10.12. An implementation MAY provide additional
information specifying what kind of protocol violation has been
detected.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cause Code=13 | Cause Length=Variable |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ Additional Information /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cause Code=13 | Cause Length=Variable |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ Additional Information /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type errorCauseProtocolViolation struct {
errorCauseHeader
additionalInformation []byte
}
// Abort chunk errors
var (
ErrProtocolViolationUnmarshal = errors.New("unable to unmarshal Protocol Violation error")
)
func (e *errorCauseProtocolViolation) marshal() ([]byte, error) {
e.raw = e.additionalInformation
return e.errorCauseHeader.marshal()
@ -36,7 +40,7 @@ func (e *errorCauseProtocolViolation) marshal() ([]byte, error) {
func (e *errorCauseProtocolViolation) unmarshal(raw []byte) error {
err := e.errorCauseHeader.unmarshal(raw)
if err != nil {
return errors.Wrap(err, "Unable to unmarshal Protocol Violation error")
return fmt.Errorf("%w: %v", ErrProtocolViolationUnmarshal, err) //nolint:errorlint
}
e.additionalInformation = e.raw

View file

@ -0,0 +1,46 @@
package sctp
import (
"fmt"
)
/*
This error cause MAY be included in ABORT chunks that are sent
because of an upper-layer request. The upper layer can specify an
Upper Layer Abort Reason that is transported by SCTP transparently
and MAY be delivered to the upper-layer protocol at the peer.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cause Code=12 | Cause Length=Variable |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ Upper Layer Abort Reason /
\ \
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type errorCauseUserInitiatedAbort struct {
errorCauseHeader
upperLayerAbortReason []byte
}
func (e *errorCauseUserInitiatedAbort) marshal() ([]byte, error) {
e.code = userInitiatedAbort
e.errorCauseHeader.raw = e.upperLayerAbortReason
return e.errorCauseHeader.marshal()
}
func (e *errorCauseUserInitiatedAbort) unmarshal(raw []byte) error {
err := e.errorCauseHeader.unmarshal(raw)
if err != nil {
return err
}
e.upperLayerAbortReason = e.errorCauseHeader.raw
return nil
}
// String makes errorCauseUserInitiatedAbort printable
func (e *errorCauseUserInitiatedAbort) String() string {
return fmt.Sprintf("%s: %s", e.errorCauseHeader.String(), e.upperLayerAbortReason)
}

View file

@ -1,14 +0,0 @@
module github.com/pion/sctp
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/pion/logging v0.2.2
github.com/pion/randutil v0.1.0
github.com/pion/transport v0.10.1
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.6.1
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
)
go 1.13

View file

@ -1,36 +0,0 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM=
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -2,10 +2,9 @@ package sctp
import (
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"github.com/pkg/errors"
)
// Create the crc32 table we'll use for the checksum
@ -20,34 +19,29 @@ Packet represents an SCTP packet, defined in https://tools.ietf.org/html/rfc4960
An SCTP packet is composed of a common header and chunks. A chunk
contains either control information or user data.
SCTP Packet Format
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Common Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Chunk #1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Chunk #n |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
SCTP Packet Format
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Common Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Chunk #1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Chunk #n |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
SCTP Common Header Format
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Value Number | Destination Value Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Verification Tag |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
SCTP Common Header Format
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Value Number | Destination Value Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Verification Tag |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
type packet struct {
sourcePort uint16
@ -60,9 +54,17 @@ const (
packetHeaderSize = 12
)
// SCTP packet errors
var (
ErrPacketRawTooSmall = errors.New("raw is smaller than the minimum length for a SCTP packet")
ErrParseSCTPChunkNotEnoughData = errors.New("unable to parse SCTP chunk, not enough data for complete header")
ErrUnmarshalUnknownChunkType = errors.New("failed to unmarshal, contains unknown chunk type")
ErrChecksumMismatch = errors.New("checksum mismatch theirs")
)
func (p *packet) unmarshal(raw []byte) error {
if len(raw) < packetHeaderSize {
return errors.Errorf("raw only %d bytes, %d is the minimum length for a SCTP packet", len(raw), packetHeaderSize)
return fmt.Errorf("%w: raw only %d bytes, %d is the minimum length", ErrPacketRawTooSmall, len(raw), packetHeaderSize)
}
p.sourcePort = binary.BigEndian.Uint16(raw[0:])
@ -75,7 +77,7 @@ func (p *packet) unmarshal(raw []byte) error {
if offset == len(raw) {
break
} else if offset+chunkHeaderSize > len(raw) {
return errors.Errorf("Unable to parse SCTP chunk, not enough data for complete header: offset %d remaining %d", offset, len(raw))
return fmt.Errorf("%w: offset %d remaining %d", ErrParseSCTPChunkNotEnoughData, offset, len(raw))
}
var c chunk
@ -102,8 +104,14 @@ func (p *packet) unmarshal(raw []byte) error {
c = &chunkForwardTSN{}
case ctError:
c = &chunkError{}
case ctShutdown:
c = &chunkShutdown{}
case ctShutdownAck:
c = &chunkShutdownAck{}
case ctShutdownComplete:
c = &chunkShutdownComplete{}
default:
return errors.Errorf("Failed to unmarshal, contains unknown chunk type %s", chunkType(raw[offset]).String())
return fmt.Errorf("%w: %s", ErrUnmarshalUnknownChunkType, chunkType(raw[offset]).String())
}
if err := c.unmarshal(raw[offset:]); err != nil {
@ -117,7 +125,7 @@ func (p *packet) unmarshal(raw []byte) error {
theirChecksum := binary.LittleEndian.Uint32(raw[8:])
ourChecksum := generatePacketChecksum(raw)
if theirChecksum != ourChecksum {
return errors.Errorf("Checksum mismatch theirs: %d ours: %d", theirChecksum, ourChecksum)
return fmt.Errorf("%w: %d ours: %d", ErrChecksumMismatch, theirChecksum, ourChecksum)
}
return nil
}

View file

@ -1,7 +1,8 @@
package sctp
import (
"github.com/pkg/errors"
"errors"
"fmt"
)
type param interface {
@ -9,12 +10,17 @@ type param interface {
length() int
}
// ErrParamTypeUnhandled is returned if unknown parameter type is specified.
var ErrParamTypeUnhandled = errors.New("unhandled ParamType")
func buildParam(t paramType, rawParam []byte) (param, error) {
switch t {
case forwardTSNSupp:
return (&paramForwardTSNSupported{}).unmarshal(rawParam)
case supportedExt:
return (&paramSupportedExtensions{}).unmarshal(rawParam)
case ecnCapable:
return (&paramECNCapable{}).unmarshal(rawParam)
case random:
return (&paramRandom{}).unmarshal(rawParam)
case reqHMACAlgo:
@ -30,6 +36,6 @@ func buildParam(t paramType, rawParam []byte) (param, error) {
case reconfigResp:
return (&paramReconfigResponse{}).unmarshal(rawParam)
default:
return nil, errors.Errorf("Unhandled ParamType %v", t)
return nil, fmt.Errorf("%w: %v", ErrParamTypeUnhandled, t)
}
}

View file

@ -0,0 +1,19 @@
package sctp
type paramECNCapable struct {
paramHeader
}
func (r *paramECNCapable) marshal() ([]byte, error) {
r.typ = ecnCapable
r.raw = []byte{}
return r.paramHeader.marshal()
}
func (r *paramECNCapable) unmarshal(raw []byte) (param, error) {
err := r.paramHeader.unmarshal(raw)
if err != nil {
return nil, err
}
return r, nil
}

View file

@ -52,7 +52,10 @@ type paramOutgoingResetRequest struct {
streamIdentifiers []uint16
}
var errSSNResetRequestParamTooShort = errors.New("outgoing SSN reset request parameter too short")
// Outgoing reset request parameter errors
var (
ErrSSNResetRequestParamTooShort = errors.New("outgoing SSN reset request parameter too short")
)
func (r *paramOutgoingResetRequest) marshal() ([]byte, error) {
r.typ = outSSNResetReq
@ -72,7 +75,7 @@ func (r *paramOutgoingResetRequest) unmarshal(raw []byte) (param, error) {
return nil, err
}
if len(r.raw) < paramOutgoingResetRequestStreamIdentifiersOffset {
return nil, errSSNResetRequestParamTooShort
return nil, ErrSSNResetRequestParamTooShort
}
r.reconfigRequestSequenceNumber = binary.BigEndian.Uint32(r.raw)
r.reconfigResponseSequenceNumber = binary.BigEndian.Uint32(r.raw[4:])

View file

@ -45,7 +45,10 @@ const (
reconfigResultInProgress reconfigResult = 6
)
var errReconfigRespParamTooShort = errors.New("reconfig response parameter too short")
// Reconfiguration response errors
var (
ErrReconfigRespParamTooShort = errors.New("reconfig response parameter too short")
)
func (t reconfigResult) String() string {
switch t {
@ -83,7 +86,7 @@ func (r *paramReconfigResponse) unmarshal(raw []byte) (param, error) {
return nil, err
}
if len(r.raw) < 8 {
return nil, errReconfigRespParamTooShort
return nil, ErrReconfigRespParamTooShort
}
r.reconfigResponseSequenceNumber = binary.BigEndian.Uint32(r.raw)
r.result = reconfigResult(binary.BigEndian.Uint32(r.raw[4:]))

View file

@ -2,9 +2,8 @@ package sctp
import (
"encoding/binary"
"errors"
"fmt"
"github.com/pkg/errors"
)
type hmacAlgorithm uint16
@ -16,6 +15,9 @@ const (
hmacSHA256 hmacAlgorithm = 3
)
// ErrInvalidAlgorithmType is returned if unknown auth algorithm is specified.
var ErrInvalidAlgorithmType = errors.New("invalid algorithm type")
func (c hmacAlgorithm) String() string {
switch c {
case hmacResv1:
@ -63,7 +65,7 @@ func (r *paramRequestedHMACAlgorithm) unmarshal(raw []byte) (param, error) {
case hmacSHA256:
r.availableAlgorithms = append(r.availableAlgorithms, a)
default:
return nil, errors.Errorf("Invalid algorithm type '%v'", a)
return nil, fmt.Errorf("%w: %v", ErrInvalidAlgorithmType, a)
}
i += 2

View file

@ -3,9 +3,8 @@ package sctp
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"github.com/pkg/errors"
)
type paramHeader struct {
@ -18,6 +17,14 @@ const (
paramHeaderLength = 4
)
// Parameter header parse errors
var (
ErrParamHeaderTooShort = errors.New("param header too short")
ErrParamHeaderSelfReportedLengthShorter = errors.New("param self reported length is shorter than header length")
ErrParamHeaderSelfReportedLengthLonger = errors.New("param self reported length is longer than header length")
ErrParamHeaderParseFailed = errors.New("failed to parse param type")
)
func (p *paramHeader) marshal() ([]byte, error) {
paramLengthPlusHeader := paramHeaderLength + len(p.raw)
@ -31,20 +38,20 @@ func (p *paramHeader) marshal() ([]byte, error) {
func (p *paramHeader) unmarshal(raw []byte) error {
if len(raw) < paramHeaderLength {
return errors.New("param header too short")
return ErrParamHeaderTooShort
}
paramLengthPlusHeader := binary.BigEndian.Uint16(raw[2:])
if int(paramLengthPlusHeader) < paramHeaderLength {
return errors.Errorf("param self reported length (%d) smaller than header length (%d)", int(paramLengthPlusHeader), paramHeaderLength)
return fmt.Errorf("%w: param self reported length (%d) shorter than header length (%d)", ErrParamHeaderSelfReportedLengthShorter, int(paramLengthPlusHeader), paramHeaderLength)
}
if len(raw) < int(paramLengthPlusHeader) {
return errors.Errorf("param length (%d) shorter than its self reported length (%d)", len(raw), int(paramLengthPlusHeader))
return fmt.Errorf("%w: param length (%d) shorter than its self reported length (%d)", ErrParamHeaderSelfReportedLengthLonger, len(raw), int(paramLengthPlusHeader))
}
typ, err := parseParamType(raw[0:])
if err != nil {
return errors.Wrap(err, "failed to parse param type")
return fmt.Errorf("%w: %v", ErrParamHeaderParseFailed, err) //nolint:errorlint
}
p.typ = typ
p.raw = raw[paramHeaderLength:paramLengthPlusHeader]

View file

@ -2,9 +2,8 @@ package sctp
import (
"encoding/binary"
"errors"
"fmt"
"github.com/pkg/errors"
)
// paramType represents a SCTP INIT/INITACK parameter
@ -25,6 +24,7 @@ const (
reconfigResp paramType = 16 // Re-configuration Response Parameter [RFC6525]
addOutStreamsReq paramType = 17 // Add Outgoing Streams Request Parameter [RFC6525]
addIncStreamsReq paramType = 18 // Add Incoming Streams Request Parameter [RFC6525]
ecnCapable paramType = 32768 // ECN Capable (0x8000) [RFC2960]
random paramType = 32770 // Random (0x8002) [RFC4805]
chunkList paramType = 32771 // Chunk List (0x8003) [RFC4895]
reqHMACAlgo paramType = 32772 // Requested HMAC Algorithm Parameter (0x8004) [RFC4895]
@ -39,9 +39,14 @@ const (
adaptLayerInd paramType = 49158 // Adaptation Layer Indication (0xC006) [RFC5061]
)
// Parameter packet errors
var (
ErrParamPacketTooShort = errors.New("packet to short")
)
func parseParamType(raw []byte) (paramType, error) {
if len(raw) < 2 {
return paramType(0), errors.New("packet to short")
return paramType(0), ErrParamPacketTooShort
}
return paramType(binary.BigEndian.Uint16(raw)), nil
}
@ -76,6 +81,8 @@ func (p paramType) String() string {
return "Add Outgoing Streams Request Parameter"
case addIncStreamsReq:
return "Add Incoming Streams Request Parameter"
case ecnCapable:
return "ECN Capable"
case random:
return "Random"
case chunkList:

View file

@ -1,7 +1,7 @@
package sctp
import (
"github.com/pkg/errors"
"errors"
)
// pendingBaseQueue
@ -48,10 +48,11 @@ type pendingQueue struct {
unorderedIsSelected bool
}
// Pending queue errors
var (
errUnexpectedChuckPoppedUnordered = errors.New("unexpected chunk popped (unordered)")
errUnexpectedChuckPoppedOrdered = errors.New("unexpected chunk popped (ordered)")
errUnexpectedQState = errors.New("unexpected q state (should've been selected)")
ErrUnexpectedChuckPoppedUnordered = errors.New("unexpected chunk popped (unordered)")
ErrUnexpectedChuckPoppedOrdered = errors.New("unexpected chunk popped (ordered)")
ErrUnexpectedQState = errors.New("unexpected q state (should've been selected)")
)
func newPendingQueue() *pendingQueue {
@ -90,12 +91,12 @@ func (q *pendingQueue) pop(c *chunkPayloadData) error {
if q.unorderedIsSelected {
popped = q.unorderedQueue.pop()
if popped != c {
return errUnexpectedChuckPoppedUnordered
return ErrUnexpectedChuckPoppedUnordered
}
} else {
popped = q.orderedQueue.pop()
if popped != c {
return errUnexpectedChuckPoppedOrdered
return ErrUnexpectedChuckPoppedOrdered
}
}
if popped.endingFragment {
@ -103,12 +104,12 @@ func (q *pendingQueue) pop(c *chunkPayloadData) error {
}
} else {
if !c.beginningFragment {
return errUnexpectedQState
return ErrUnexpectedQState
}
if c.unordered {
popped := q.unorderedQueue.pop()
if popped != c {
return errUnexpectedChuckPoppedUnordered
return ErrUnexpectedChuckPoppedUnordered
}
if !popped.endingFragment {
q.selected = true
@ -117,7 +118,7 @@ func (q *pendingQueue) pop(c *chunkPayloadData) error {
} else {
popped := q.orderedQueue.pop()
if popped != c {
return errUnexpectedChuckPoppedOrdered
return ErrUnexpectedChuckPoppedOrdered
}
if !popped.endingFragment {
q.selected = true

View file

@ -1,11 +1,10 @@
package sctp
import (
"errors"
"io"
"sort"
"sync/atomic"
"github.com/pkg/errors"
)
func sortChunksByTSN(a []*chunkPayloadData) {

View file

@ -1,15 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"postUpdateOptions": [
"gomodTidy"
],
"commitBody": "Generated by renovateBot",
"packageRules": [
{
"packagePatterns": ["^golang.org/x/"],
"schedule": ["on the first day of the month"]
}
"github>pion/renovate-config"
]
}

View file

@ -1,12 +1,16 @@
package sctp
import (
"errors"
"fmt"
"io"
"math"
"os"
"sync"
"sync/atomic"
"time"
"github.com/pion/logging"
"github.com/pkg/errors"
)
const (
@ -18,6 +22,36 @@ const (
ReliabilityTypeTimed byte = 2
)
// StreamState is an enum for SCTP Stream state field
// This field identifies the state of stream.
type StreamState int
// StreamState enums
const (
StreamStateOpen StreamState = iota // Stream object starts with StreamStateOpen
StreamStateClosing // Outgoing stream is being reset
StreamStateClosed // Stream has been closed
)
func (ss StreamState) String() string {
switch ss {
case StreamStateOpen:
return "open"
case StreamStateClosing:
return "closing"
case StreamStateClosed:
return "closed"
}
return "unknown"
}
// SCTP stream errors
var (
ErrOutboundPacketTooLarge = errors.New("outbound packet larger than maximum message size")
ErrStreamClosed = errors.New("stream closed")
ErrReadDeadlineExceeded = fmt.Errorf("read deadline exceeded: %w", os.ErrDeadlineExceeded)
)
// Stream represents an SCTP stream
type Stream struct {
association *Association
@ -28,13 +62,14 @@ type Stream struct {
sequenceNumber uint16
readNotifier *sync.Cond
readErr error
writeErr error
readTimeoutCancel chan struct{}
unordered bool
reliabilityType byte
reliabilityValue uint32
bufferedAmount uint64
bufferedAmountLow uint64
onBufferedAmountLow func()
state StreamState
log logging.LeveledLogger
name string
}
@ -48,15 +83,7 @@ func (s *Stream) StreamIdentifier() uint16 {
// SetDefaultPayloadType sets the default payload type used by Write.
func (s *Stream) SetDefaultPayloadType(defaultPayloadType PayloadProtocolIdentifier) {
s.lock.Lock()
defer s.lock.Unlock()
s.setDefaultPayloadType(defaultPayloadType)
}
// setDefaultPayloadType sets the defaultPayloadType. The caller should hold the lock.
func (s *Stream) setDefaultPayloadType(defaultPayloadType PayloadProtocolIdentifier) {
s.defaultPayloadType = defaultPayloadType
atomic.StoreUint32((*uint32)(&s.defaultPayloadType), uint32(defaultPayloadType))
}
// SetReliabilityParams sets reliability parameters for this stream.
@ -93,6 +120,14 @@ func (s *Stream) ReadSCTP(p []byte) (int, PayloadProtocolIdentifier, error) {
s.lock.Lock()
defer s.lock.Unlock()
defer func() {
// close readTimeoutCancel if the current read timeout routine is no longer effective
if s.readTimeoutCancel != nil && s.readErr != nil {
close(s.readTimeoutCancel)
s.readTimeoutCancel = nil
}
}()
for {
n, ppi, err := s.reassemblyQueue.read(p)
if err == nil {
@ -110,6 +145,47 @@ func (s *Stream) ReadSCTP(p []byte) (int, PayloadProtocolIdentifier, error) {
}
}
// SetReadDeadline sets the read deadline in an identical way to net.Conn
func (s *Stream) SetReadDeadline(deadline time.Time) error {
s.lock.Lock()
defer s.lock.Unlock()
if s.readTimeoutCancel != nil {
close(s.readTimeoutCancel)
s.readTimeoutCancel = nil
}
if s.readErr != nil {
if !errors.Is(s.readErr, ErrReadDeadlineExceeded) {
return nil
}
s.readErr = nil
}
if !deadline.IsZero() {
s.readTimeoutCancel = make(chan struct{})
go func(readTimeoutCancel chan struct{}) {
t := time.NewTimer(time.Until(deadline))
select {
case <-readTimeoutCancel:
t.Stop()
return
case <-t.C:
s.lock.Lock()
if s.readErr == nil {
s.readErr = ErrReadDeadlineExceeded
}
s.readTimeoutCancel = nil
s.lock.Unlock()
s.readNotifier.Signal()
}
}(s.readTimeoutCancel)
}
return nil
}
func (s *Stream) handleData(pd *chunkPayloadData) {
s.lock.Lock()
defer s.lock.Unlock()
@ -174,26 +250,28 @@ func (s *Stream) handleForwardTSNForUnordered(newCumulativeTSN uint32) {
// Write writes len(p) bytes from p with the default Payload Protocol Identifier
func (s *Stream) Write(p []byte) (n int, err error) {
return s.WriteSCTP(p, s.defaultPayloadType)
ppi := PayloadProtocolIdentifier(atomic.LoadUint32((*uint32)(&s.defaultPayloadType)))
return s.WriteSCTP(p, ppi)
}
// WriteSCTP writes len(p) bytes from p to the DTLS connection
func (s *Stream) WriteSCTP(p []byte, ppi PayloadProtocolIdentifier) (n int, err error) {
func (s *Stream) WriteSCTP(p []byte, ppi PayloadProtocolIdentifier) (int, error) {
maxMessageSize := s.association.MaxMessageSize()
if len(p) > int(maxMessageSize) {
return 0, errors.Errorf("Outbound packet larger than maximum message size %v", math.MaxUint16)
return 0, fmt.Errorf("%w: %v", ErrOutboundPacketTooLarge, math.MaxUint16)
}
s.lock.RLock()
err = s.writeErr
s.lock.RUnlock()
if err != nil {
return 0, err
if s.State() != StreamStateOpen {
return 0, ErrStreamClosed
}
chunks := s.packetize(p, ppi)
return len(p), s.association.sendPayloadData(chunks)
n := len(p)
err := s.association.sendPayloadData(chunks)
if err != nil {
return n, ErrStreamClosed
}
return n, nil
}
func (s *Stream) packetize(raw []byte, ppi PayloadProtocolIdentifier) []*chunkPayloadData {
@ -257,26 +335,23 @@ func (s *Stream) packetize(raw []byte, ppi PayloadProtocolIdentifier) []*chunkPa
// Close closes the write-direction of the stream.
// Future calls to Write are not permitted after calling Close.
func (s *Stream) Close() error {
if sid, isOpen := func() (uint16, bool) {
if sid, resetOutbound := func() (uint16, bool) {
s.lock.Lock()
defer s.lock.Unlock()
isOpen := true
if s.writeErr == nil {
s.writeErr = errors.New("Stream closed")
} else {
isOpen = false
}
s.log.Debugf("[%s] Close: state=%s", s.name, s.state.String())
if s.readErr == nil {
s.readErr = io.EOF
} else {
isOpen = false
if s.state == StreamStateOpen {
if s.readErr == nil {
s.state = StreamStateClosing
} else {
s.state = StreamStateClosed
}
s.log.Debugf("[%s] state change: open => %s", s.name, s.state.String())
return s.streamIdentifier, true
}
s.readNotifier.Broadcast() // broadcast regardless
return s.streamIdentifier, isOpen
}(); isOpen {
return s.streamIdentifier, false
}(); resetOutbound {
// Reset the outgoing stream
// https://tools.ietf.org/html/rfc6525
return s.association.sendResetRequest(sid)
@ -355,3 +430,35 @@ func (s *Stream) getNumBytesInReassemblyQueue() int {
// No lock is required as it reads the size with atomic load function.
return s.reassemblyQueue.getNumBytes()
}
func (s *Stream) onInboundStreamReset() {
s.lock.Lock()
defer s.lock.Unlock()
s.log.Debugf("[%s] onInboundStreamReset: state=%s", s.name, s.state.String())
// No more inbound data to read. Unblock the read with io.EOF.
// This should cause DCEP layer (datachannel package) to call Close() which
// will reset outgoing stream also.
// See RFC 8831 section 6.7:
// if one side decides to close the data channel, it resets the corresponding
// outgoing stream. When the peer sees that an incoming stream was
// reset, it also resets its corresponding outgoing stream. Once this
// is completed, the data channel is closed.
s.readErr = io.EOF
s.readNotifier.Broadcast()
if s.state == StreamStateClosing {
s.log.Debugf("[%s] state change: closing => closed", s.name)
s.state = StreamStateClosed
}
}
// State return the stream state.
func (s *Stream) State() StreamState {
s.lock.RLock()
defer s.lock.RUnlock()
return s.state
}

View file

@ -4,8 +4,8 @@ const (
paddingMultiple = 4
)
func getPadding(len int) int {
return (paddingMultiple - (len % paddingMultiple)) % paddingMultiple
func getPadding(l int) int {
return (paddingMultiple - (l % paddingMultiple)) % paddingMultiple
}
func padByte(in []byte, cnt int) []byte {