diff --git a/icecontrol.go b/icecontrol.go new file mode 100644 index 00000000..5d6f5a1b --- /dev/null +++ b/icecontrol.go @@ -0,0 +1,83 @@ +package ice + +import "github.com/gortc/stun" + +// tiebreaker is common helper for ICE-{CONTROLLED,CONTROLLING} +// and represents the so-called tiebreaker number. +type tiebreaker uint64 + +const tiebreakerSize = 8 // 64 bit + +// AddToAs adds tiebreaker value to m as t attribute. +func (a tiebreaker) AddToAs(m *stun.Message, t stun.AttrType) error { + v := make([]byte, tiebreakerSize) + bin.PutUint64(v, uint64(a)) + m.Add(t, v) + return nil +} + +// GetFromAs decodes tiebreaker value in message getting it as for t type. +func (a *tiebreaker) GetFromAs(m *stun.Message, t stun.AttrType) error { + v, err := m.Get(t) + if err != nil { + return err + } + if err = stun.CheckSize(t, len(v), tiebreakerSize); err != nil { + return err + } + *a = tiebreaker(bin.Uint64(v)) + return nil +} + +// AttrControlled represents ICE-CONTROLLED attribute. +type AttrControlled uint64 + +// AddTo adds ICE-CONTROLLED to message. +func (c AttrControlled) AddTo(m *stun.Message) error { + return tiebreaker(c).AddToAs(m, stun.AttrICEControlled) +} + +// GetFrom decodes ICE-CONTROLLED from message. +func (c *AttrControlled) GetFrom(m *stun.Message) error { + return (*tiebreaker)(c).GetFromAs(m, stun.AttrICEControlled) +} + +// AttrControlling represents ICE-CONTROLLING attribute. +type AttrControlling uint64 + +// AddTo adds ICE-CONTROLLING to message. +func (c AttrControlling) AddTo(m *stun.Message) error { + return tiebreaker(c).AddToAs(m, stun.AttrICEControlling) +} + +// GetFrom decodes ICE-CONTROLLING from message. +func (c *AttrControlling) GetFrom(m *stun.Message) error { + return (*tiebreaker)(c).GetFromAs(m, stun.AttrICEControlling) +} + +// AttrControl is helper that wraps ICE-{CONTROLLED,CONTROLLING}. +type AttrControl struct { + Role Role + Tiebreaker uint64 +} + +// AddTo adds ICE-CONTROLLED or ICE-CONTROLLING attribute depending on Role. +func (c AttrControl) AddTo(m *stun.Message) error { + if c.Role == Controlling { + return tiebreaker(c.Tiebreaker).AddToAs(m, stun.AttrICEControlling) + } + return tiebreaker(c.Tiebreaker).AddToAs(m, stun.AttrICEControlled) +} + +// GetFrom decodes Role and Tiebreaker value from message. +func (c *AttrControl) GetFrom(m *stun.Message) error { + if m.Contains(stun.AttrICEControlling) { + c.Role = Controlling + return (*tiebreaker)(&c.Tiebreaker).GetFromAs(m, stun.AttrICEControlling) + } + if m.Contains(stun.AttrICEControlled) { + c.Role = Controlled + return (*tiebreaker)(&c.Tiebreaker).GetFromAs(m, stun.AttrICEControlled) + } + return stun.ErrAttributeNotFound +} diff --git a/icecontrol_test.go b/icecontrol_test.go new file mode 100644 index 00000000..314310bf --- /dev/null +++ b/icecontrol_test.go @@ -0,0 +1,139 @@ +package ice + +import ( + "testing" + + "github.com/gortc/stun" +) + +func TestControlled_GetFrom(t *testing.T) { + m := new(stun.Message) + var c AttrControlled + if err := c.GetFrom(m); err != stun.ErrAttributeNotFound { + t.Error("unexpected error") + } + if err := m.Build(stun.BindingRequest, &c); err != nil { + t.Error(err) + } + m1 := new(stun.Message) + if _, err := m1.Write(m.Raw); err != nil { + t.Error(err) + } + var c1 AttrControlled + if err := c1.GetFrom(m1); err != nil { + t.Error(err) + } + if c1 != c { + t.Error("not equal") + } + t.Run("IncorrectSize", func(t *testing.T) { + m3 := new(stun.Message) + m3.Add(stun.AttrICEControlled, make([]byte, 100)) + var c2 AttrControlled + if err := c2.GetFrom(m3); !stun.IsAttrSizeInvalid(err) { + t.Error("should error") + } + }) +} + +func TestControlling_GetFrom(t *testing.T) { + m := new(stun.Message) + var c AttrControlling + if err := c.GetFrom(m); err != stun.ErrAttributeNotFound { + t.Error("unexpected error") + } + if err := m.Build(stun.BindingRequest, &c); err != nil { + t.Error(err) + } + m1 := new(stun.Message) + if _, err := m1.Write(m.Raw); err != nil { + t.Error(err) + } + var c1 AttrControlling + if err := c1.GetFrom(m1); err != nil { + t.Error(err) + } + if c1 != c { + t.Error("not equal") + } + t.Run("IncorrectSize", func(t *testing.T) { + m3 := new(stun.Message) + m3.Add(stun.AttrICEControlling, make([]byte, 100)) + var c2 AttrControlling + if err := c2.GetFrom(m3); !stun.IsAttrSizeInvalid(err) { + t.Error("should error") + } + }) +} + +func TestControl_GetFrom(t *testing.T) { + t.Run("Blank", func(t *testing.T) { + m := new(stun.Message) + var c AttrControl + if err := c.GetFrom(m); err != stun.ErrAttributeNotFound { + t.Error("unexpected error") + } + }) + t.Run("Controlling", func(t *testing.T) { + m := new(stun.Message) + var c AttrControl + if err := c.GetFrom(m); err != stun.ErrAttributeNotFound { + t.Error("unexpected error") + } + c.Role = Controlling + c.Tiebreaker = 4321 + if err := m.Build(stun.BindingRequest, &c); err != nil { + t.Error(err) + } + m1 := new(stun.Message) + if _, err := m1.Write(m.Raw); err != nil { + t.Error(err) + } + var c1 AttrControl + if err := c1.GetFrom(m1); err != nil { + t.Error(err) + } + if c1 != c { + t.Error("not equal") + } + t.Run("IncorrectSize", func(t *testing.T) { + m3 := new(stun.Message) + m3.Add(stun.AttrICEControlling, make([]byte, 100)) + var c2 AttrControl + if err := c2.GetFrom(m3); !stun.IsAttrSizeInvalid(err) { + t.Error("should error") + } + }) + }) + t.Run("Controlled", func(t *testing.T) { + m := new(stun.Message) + var c AttrControl + if err := c.GetFrom(m); err != stun.ErrAttributeNotFound { + t.Error("unexpected error") + } + c.Role = Controlled + c.Tiebreaker = 1234 + if err := m.Build(stun.BindingRequest, &c); err != nil { + t.Error(err) + } + m1 := new(stun.Message) + if _, err := m1.Write(m.Raw); err != nil { + t.Error(err) + } + var c1 AttrControl + if err := c1.GetFrom(m1); err != nil { + t.Error(err) + } + if c1 != c { + t.Error("not equal") + } + t.Run("IncorrectSize", func(t *testing.T) { + m3 := new(stun.Message) + m3.Add(stun.AttrICEControlling, make([]byte, 100)) + var c2 AttrControl + if err := c2.GetFrom(m3); !stun.IsAttrSizeInvalid(err) { + t.Error("should error") + } + }) + }) +} diff --git a/priority.go b/priority.go new file mode 100644 index 00000000..2756d9ba --- /dev/null +++ b/priority.go @@ -0,0 +1,29 @@ +package ice + +import "github.com/gortc/stun" + +// PriorityAttr represents PRIORITY attribute. +type PriorityAttr uint32 + +const prioritySize = 4 // 32 bit + +// AddTo adds PRIORITY attribute to message. +func (p PriorityAttr) AddTo(m *stun.Message) error { + v := make([]byte, prioritySize) + bin.PutUint32(v, uint32(p)) + m.Add(stun.AttrPriority, v) + return nil +} + +// GetFrom decodes PRIORITY attribute from message. +func (p *PriorityAttr) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrPriority) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrPriority, len(v), prioritySize); err != nil { + return err + } + *p = PriorityAttr(bin.Uint32(v)) + return nil +} diff --git a/priority_test.go b/priority_test.go new file mode 100644 index 00000000..8cfa67fe --- /dev/null +++ b/priority_test.go @@ -0,0 +1,37 @@ +package ice + +import ( + "testing" + + "github.com/gortc/stun" +) + +func TestPriority_GetFrom(t *testing.T) { + m := new(stun.Message) + var p PriorityAttr + if err := p.GetFrom(m); err != stun.ErrAttributeNotFound { + t.Error("unexpected error") + } + if err := m.Build(stun.BindingRequest, &p); err != nil { + t.Error(err) + } + m1 := new(stun.Message) + if _, err := m1.Write(m.Raw); err != nil { + t.Error(err) + } + var p1 PriorityAttr + if err := p1.GetFrom(m1); err != nil { + t.Error(err) + } + if p1 != p { + t.Error("not equal") + } + t.Run("IncorrectSize", func(t *testing.T) { + m3 := new(stun.Message) + m3.Add(stun.AttrPriority, make([]byte, 100)) + var p2 PriorityAttr + if err := p2.GetFrom(m3); !stun.IsAttrSizeInvalid(err) { + t.Error("should error") + } + }) +} diff --git a/usecandidate.go b/usecandidate.go new file mode 100644 index 00000000..c9856c71 --- /dev/null +++ b/usecandidate.go @@ -0,0 +1,21 @@ +package ice + +import "github.com/gortc/stun" + +// UseCandidateAttr represents USE-CANDIDATE attribute. +type UseCandidateAttr struct{} + +// AddTo adds USE-CANDIDATE attribute to message. +func (UseCandidateAttr) AddTo(m *stun.Message) error { + m.Add(stun.AttrUseCandidate, nil) + return nil +} + +// IsSet returns true if USE-CANDIDATE attribute is set. +func (UseCandidateAttr) IsSet(m *stun.Message) bool { + _, err := m.Get(stun.AttrUseCandidate) + return err == nil +} + +// UseCandidate is shorthand for UseCandidateAttr. +var UseCandidate UseCandidateAttr diff --git a/usecandidate_test.go b/usecandidate_test.go new file mode 100644 index 00000000..a5168c46 --- /dev/null +++ b/usecandidate_test.go @@ -0,0 +1,24 @@ +package ice + +import ( + "testing" + + "github.com/gortc/stun" +) + +func TestUseCandidateAttr_AddTo(t *testing.T) { + m := new(stun.Message) + if UseCandidate.IsSet(m) { + t.Error("should not be set") + } + if err := m.Build(stun.BindingRequest, UseCandidate); err != nil { + t.Error(err) + } + m1 := new(stun.Message) + if _, err := m1.Write(m.Raw); err != nil { + t.Error(err) + } + if !UseCandidate.IsSet(m1) { + t.Error("should be set") + } +}