Add more association logic, RFC stuff

This commit is contained in:
John R. Bradley
2018-07-09 17:55:33 -05:00
committed by Sean DuBois
parent e8b58bf959
commit 93fa676740
6 changed files with 185 additions and 15 deletions

View File

@@ -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 {

View File

@@ -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
}

View 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
}

View 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)
}

View File

@@ -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
}

View File

@@ -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
}