mirror of
https://github.com/pion/webrtc.git
synced 2025-11-03 09:40:59 +08:00
Add more association logic, RFC stuff
This commit is contained in:
committed by
Sean DuBois
parent
e8b58bf959
commit
93fa676740
@@ -104,21 +104,13 @@ func (p *Port) handleICE(in *incomingPacket, remoteKey []byte, iceTimer *time.Ti
|
||||
}
|
||||
|
||||
func (p *Port) handleSCTP(raw []byte, dtlsState *dtls.State) {
|
||||
s := &sctp.Packet{}
|
||||
if err := s.Unmarshal(raw); err != nil {
|
||||
p := &sctp.Packet{}
|
||||
if err := p.Unmarshal(raw); err != nil {
|
||||
fmt.Println(errors.Wrap(err, "Failed to Unmarshal SCTP packet"))
|
||||
return
|
||||
}
|
||||
|
||||
for _, c := range s.Chunks {
|
||||
switch v := c.(type) {
|
||||
case *sctp.Init:
|
||||
fmt.Println("Got an init chunk")
|
||||
dtlsState.Send([]byte{0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08})
|
||||
default:
|
||||
fmt.Println("Unhandled chunk!", v)
|
||||
}
|
||||
}
|
||||
p.association.PushPacket(p)
|
||||
}
|
||||
|
||||
func (p *Port) handleDTLS(raw []byte, srcAddr *net.UDPAddr, certPair *dtls.CertPair) bool {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/pions/pkg/stun"
|
||||
"github.com/pions/webrtc/internal/dtls"
|
||||
"github.com/pions/webrtc/internal/sctp"
|
||||
"github.com/pions/webrtc/internal/srtp"
|
||||
"github.com/pions/webrtc/pkg/ice"
|
||||
"github.com/pions/webrtc/pkg/rtp"
|
||||
@@ -37,6 +38,8 @@ type Port struct {
|
||||
srtpContextsLock *sync.Mutex
|
||||
srtpContexts map[string]*srtp.Context
|
||||
|
||||
association *sctp.Association
|
||||
|
||||
conn *ipv4.PacketConn
|
||||
}
|
||||
|
||||
|
||||
118
internal/sctp/association.go
Normal file
118
internal/sctp/association.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package sctp
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
// https://tools.ietf.org/html/rfc4960#section-13.2
|
||||
type AssociationState uint8
|
||||
|
||||
const (
|
||||
CookieWait AssociationState = iota
|
||||
CookieEchoed
|
||||
Established
|
||||
ShutdownPending
|
||||
ShutdownSent
|
||||
ShutdownReceived
|
||||
ShutdownAckSent
|
||||
)
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
|
||||
type Association struct {
|
||||
PeerVerificationTag uint32
|
||||
MyVerificationTag uint32
|
||||
State AssociationState
|
||||
|
||||
// Non-RFC internal data
|
||||
myMaxNumInboundStreams uint16
|
||||
myMaxNumOutboundStreams uint16
|
||||
}
|
||||
|
||||
func hasPacket(p *Packet, t ChunkType) bool {
|
||||
for _, c := range p.Chunks {
|
||||
if c.Type() == t {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkPacket(p *Packet) error {
|
||||
for _, c := range p.Chunks {
|
||||
switch c.(type) {
|
||||
case *Init:
|
||||
// An INIT or INIT ACK chunk MUST NOT be bundled with any other chunk.
|
||||
// They MUST be the only chunks present in the SCTP packets that carry
|
||||
// them.
|
||||
if len(p.Chunks) != 1 {
|
||||
return errors.New("INIT chunk must not be bundled with any other chunk")
|
||||
}
|
||||
|
||||
if p.VerificationTag != 0 {
|
||||
return errors.Errorf("INIT chunk expects a verification tag of 0 on the packet when out-of-the-blue")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func min(a, b uint16) uint16 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (a *Association) PushPacket(p *Packet) error {
|
||||
err := checkPacket(p)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed validating packet")
|
||||
}
|
||||
|
||||
for _, c := range p.Chunks {
|
||||
a.handleChunk(c)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Association) handleChunk(c Chunk) error {
|
||||
err := c.Check()
|
||||
if err != nil {
|
||||
errors.Wrap(err, "Failed validating chunk")
|
||||
// TODO: Create ABORT
|
||||
}
|
||||
|
||||
switch ct := c.(type) {
|
||||
case *Init:
|
||||
if a.State != CookieEchoed && a.State != CookieWait {
|
||||
// https://tools.ietf.org/html/rfc4960#section-5.2.1
|
||||
// 5.2.1. INIT Received in COOKIE-WAIT or COOKIE-ECHOED State (Item B)
|
||||
a.myMaxNumInboundStreams = min(ct.numInboundStreams, a.myMaxNumInboundStreams)
|
||||
a.myMaxNumOutboundStreams = min(ct.numOutboundStreams, a.myMaxNumOutboundStreams)
|
||||
} else {
|
||||
//https://tools.ietf.org/html/rfc4960#section-5.2.2
|
||||
// 5.2.2. Unexpected INIT in States Other than CLOSED, COOKIE-ECHOED,
|
||||
// COOKIE-WAIT, and SHUTDOWN-ACK-SENT
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
22
internal/sctp/association_test.go
Normal file
22
internal/sctp/association_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package sctp
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAssociationInit(t *testing.T) {
|
||||
pkt := &Packet{}
|
||||
rawPkt := []byte{0x13, 0x88, 0x13, 0x88, 0x00, 0x00, 0x00, 0x00, 0x81, 0x46, 0x9d, 0xfc, 0x01, 0x00, 0x00, 0x56, 0x55,
|
||||
0xb9, 0x64, 0xa5, 0x00, 0x02, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0xe8, 0x6d, 0x10, 0x30, 0xc0, 0x00, 0x00, 0x04, 0x80,
|
||||
0x08, 0x00, 0x09, 0xc0, 0x0f, 0xc1, 0x80, 0x82, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x24, 0x9f, 0xeb, 0xbb, 0x5c, 0x50,
|
||||
0xc9, 0xbf, 0x75, 0x9c, 0xb1, 0x2c, 0x57, 0x4f, 0xa4, 0x5a, 0x51, 0xba, 0x60, 0x17, 0x78, 0x27, 0x94, 0x5c, 0x31, 0xe6,
|
||||
0x5d, 0x5b, 0x09, 0x47, 0xe2, 0x22, 0x06, 0x80, 0x04, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x80, 0x03, 0x00, 0x06, 0x80, 0xc1, 0x00, 0x00}
|
||||
err := pkt.Unmarshal(rawPkt)
|
||||
if err != nil {
|
||||
t.Error(errors.Wrap(err, "Unmarshal failed, has chunk"))
|
||||
}
|
||||
|
||||
assoc := &Association{}
|
||||
assoc.PushPacket(pkt)
|
||||
}
|
||||
@@ -83,7 +83,7 @@ Value field.
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
type ChunkHeader struct {
|
||||
Type ChunkType
|
||||
typ ChunkType
|
||||
Flags byte
|
||||
Length uint16
|
||||
Value []byte
|
||||
@@ -98,7 +98,7 @@ func (c *ChunkHeader) unmarshalHeader(raw []byte) error {
|
||||
return errors.Errorf("raw only %d bytes, %d is the minimum length for a SCTP chunk", len(raw), chunkHeaderSize)
|
||||
}
|
||||
|
||||
c.Type = ChunkType(raw[0])
|
||||
c.typ = ChunkType(raw[0])
|
||||
c.Flags = byte(raw[1])
|
||||
c.Length = binary.BigEndian.Uint16(raw[2:])
|
||||
|
||||
@@ -130,6 +130,10 @@ func (c *ChunkHeader) unmarshalHeader(raw []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ChunkHeader) Type() ChunkType {
|
||||
return c.typ
|
||||
}
|
||||
|
||||
func (c *ChunkHeader) valueLength() int {
|
||||
return len(c.Value)
|
||||
}
|
||||
@@ -138,6 +142,8 @@ func (c *ChunkHeader) valueLength() int {
|
||||
type Chunk interface {
|
||||
Unmarshal(raw []byte) error
|
||||
Marshal() ([]byte, error)
|
||||
Type() ChunkType
|
||||
Check() error
|
||||
|
||||
valueLength() int
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ func (i *Init) Unmarshal(raw []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if i.Type != INIT {
|
||||
return errors.Errorf("ChunkType is not of type INIT, actually is %s", i.Type.String())
|
||||
if i.typ != INIT {
|
||||
return errors.Errorf("ChunkType is not of type INIT, actually is %s", i.typ.String())
|
||||
} else if len(i.Value) < initChunkMinLength {
|
||||
return errors.Errorf("Chunk Value isn't long enough for mandatory parameters exp: %d actual: %d", initChunkMinLength, len(i.Value))
|
||||
}
|
||||
@@ -151,3 +151,32 @@ func (i *Init) Unmarshal(raw []byte) error {
|
||||
func (i *Init) Marshal() ([]byte, error) {
|
||||
return nil, errors.Errorf("Unimplemented")
|
||||
}
|
||||
|
||||
func (i *Init) Check() error {
|
||||
// Defines the maximum number of streams the sender of this INIT
|
||||
// chunk allows the peer end to create in this association. The
|
||||
// value 0 MUST NOT be used.
|
||||
//
|
||||
// Note: There is no negotiation of the actual number of streams but
|
||||
// instead the two endpoints will use the min(requested, offered).
|
||||
// See Section 5.1.1 for details.
|
||||
//
|
||||
// Note: A receiver of an INIT with the MIS value of 0 SHOULD abort
|
||||
// the association.
|
||||
if i.numInboundStreams == 0 {
|
||||
return errors.New("INIT inbound stream request must be > 0")
|
||||
}
|
||||
|
||||
// Defines the number of outbound streams the sender of this INIT
|
||||
// chunk wishes to create in this association. The value of 0 MUST
|
||||
// NOT be used.
|
||||
//
|
||||
// Note: A receiver of an INIT with the OS value set to 0 SHOULD
|
||||
// abort the association.
|
||||
|
||||
if i.numOutboundStreams == 0 {
|
||||
return errors.New("INIT outbound stream request must be > 0")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user