mirror of
https://github.com/pion/stun.git
synced 2025-10-14 20:05:49 +08:00
168 lines
4.9 KiB
Go
168 lines
4.9 KiB
Go
package stun
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"net"
|
||
|
||
"github.com/pkg/errors"
|
||
)
|
||
|
||
// blank is just blank string and exists just because it is ugly to keep it
|
||
// in code.
|
||
const blank = ""
|
||
|
||
func (m *Message) getAttrValue(t AttrType) []byte {
|
||
return m.Attributes.Get(t).Value
|
||
}
|
||
|
||
// AddSoftwareBytes adds SOFTWARE attribute with value from byte slice.
|
||
func (m *Message) AddSoftwareBytes(software []byte) {
|
||
m.Add(AttrSoftware, software)
|
||
}
|
||
|
||
// AddSoftware adds SOFTWARE attribute with value from string.
|
||
func (m *Message) AddSoftware(software string) {
|
||
m.Add(AttrSoftware, []byte(software))
|
||
}
|
||
|
||
// GetSoftwareBytes returns SOFTWARE attribute value in byte slice.
|
||
// If not found, returns nil.
|
||
func (m *Message) GetSoftwareBytes() []byte {
|
||
return m.Attributes.Get(AttrSoftware).Value
|
||
}
|
||
|
||
// GetSoftware returns SOFTWARE attribute value in string.
|
||
// If not found, returns blank string.
|
||
func (m *Message) GetSoftware() string {
|
||
v := m.GetSoftwareBytes()
|
||
if len(v) == 0 {
|
||
return blank
|
||
}
|
||
return string(v)
|
||
}
|
||
|
||
// Address family values.
|
||
const (
|
||
FamilyIPv4 byte = 0x01
|
||
FamilyIPv6 byte = 0x02
|
||
)
|
||
|
||
// AddXORMappedAddress adds XOR MAPPED ADDRESS attribute to message.
|
||
func (m *Message) AddXORMappedAddress(ip net.IP, port int) {
|
||
// X-Port is computed by taking the mapped port in host byte order,
|
||
// XOR’ing it with the most significant 16 bits of the magic cookie, and
|
||
// then the converting the result to network byte order.
|
||
value := make([]byte, 32+128)
|
||
value[0] = 0 // first 8 bits are zeroes
|
||
family := FamilyIPv4
|
||
if len(ip) == net.IPv6len {
|
||
family = FamilyIPv6
|
||
}
|
||
binary.BigEndian.PutUint16(value[0:2], uint16(family))
|
||
port ^= magicCookie >> 16
|
||
binary.BigEndian.PutUint16(value[2:4], uint16(port))
|
||
xorValue := make([]byte, 128)
|
||
binary.BigEndian.PutUint32(xorValue[0:4], magicCookie)
|
||
copy(xorValue[4:], m.TransactionID[:])
|
||
xorBytes(value[4:4+len(ip)], ip, xorValue)
|
||
|
||
m.Add(AttrXORMappedAddress, value[:4+len(ip)])
|
||
}
|
||
|
||
func (m *Message) allocBuffer(size int) []byte {
|
||
capacity := len(m.buf.B) + size
|
||
m.grow(capacity)
|
||
m.buf.B = m.buf.B[:capacity]
|
||
return m.buf.B[len(m.buf.B)-size:]
|
||
}
|
||
|
||
// GetXORMappedAddress returns ip, port from attribute and error if any.
|
||
// Value for ip is valid until Message is released or underlying buffer is
|
||
// corrupted.
|
||
func (m *Message) GetXORMappedAddress() (net.IP, int, error) {
|
||
// X-Port is computed by taking the mapped port in host byte order,
|
||
// XOR’ing it with the most significant 16 bits of the magic cookie, and
|
||
// then the converting the result to network byte order.
|
||
v := m.getAttrValue(AttrXORMappedAddress)
|
||
if len(v) == 0 {
|
||
return nil, 0, errors.Wrap(ErrAttributeNotFound, "not found")
|
||
}
|
||
family := byte(binary.BigEndian.Uint16(v[0:2]))
|
||
if family != FamilyIPv6 && family != FamilyIPv4 {
|
||
err := errors.Wrapf(ErrAttributeDecodeError, "bad family %d", family)
|
||
return nil, 0, err
|
||
}
|
||
ipLen := net.IPv4len
|
||
if family == FamilyIPv6 {
|
||
ipLen = net.IPv6len
|
||
}
|
||
ip := net.IP(m.allocBuffer(ipLen))
|
||
port := int(binary.BigEndian.Uint16(v[2:4])) ^ (magicCookie >> 16)
|
||
xorValue := make([]byte, 128)
|
||
binary.BigEndian.PutUint32(xorValue[0:4], magicCookie)
|
||
copy(xorValue[4:], m.TransactionID[:])
|
||
xorBytes(ip, v[4:], xorValue)
|
||
return ip, port, nil
|
||
}
|
||
|
||
// shouldBeLess panics with err if s is not less than n characters.
|
||
func shouldBeLess(s string, n int, err error) {
|
||
if len(s) >= n {
|
||
panic(err)
|
||
}
|
||
}
|
||
|
||
// constants for ERROR-CODE encoding.
|
||
const (
|
||
errorCodeReasonStart = 4
|
||
errorCodeClassByte = 2
|
||
errorCodeNumberByte = 3
|
||
errorCodeReasonMax = 128
|
||
errorCodeReasonMaxB = 763
|
||
errorCodeModulo = 100
|
||
)
|
||
|
||
// AddErrorCode adds ERROR-CODE attribute to message.
|
||
//
|
||
// The reason phrase MUST be a UTF-8 [RFC 3629] encoded
|
||
// sequence of less than 128 characters (which can be as long as 763
|
||
// bytes).
|
||
func (m *Message) AddErrorCode(code int, reason string) {
|
||
shouldBeLess(reason, errorCodeReasonMax, ErrErrorTooLong)
|
||
value := make([]byte,
|
||
errorCodeReasonStart, errorCodeReasonMaxB+errorCodeReasonStart,
|
||
)
|
||
number := byte(code % errorCodeModulo) // error code modulo 100
|
||
class := byte(code / errorCodeModulo) // hundred digit
|
||
value[errorCodeClassByte] = class
|
||
value[errorCodeNumberByte] = number
|
||
value = append(value, reason...)
|
||
m.Add(AttrErrorCode, value)
|
||
}
|
||
|
||
// GetErrorCode returns ERROR-CODE code, reason and decode error if any.
|
||
func (m *Message) GetErrorCode() (int, []byte, error) {
|
||
v := m.getAttrValue(AttrErrorCode)
|
||
if len(v) == 0 {
|
||
return 0, nil, errors.Wrap(ErrAttributeNotFound, "not found")
|
||
}
|
||
var (
|
||
class = uint16(v[errorCodeClassByte])
|
||
number = uint16(v[errorCodeNumberByte])
|
||
code = int(class*errorCodeModulo + number)
|
||
reason = v[errorCodeReasonStart:]
|
||
)
|
||
return code, reason, nil
|
||
}
|
||
|
||
var (
|
||
// ErrAttributeNotFound means that there is no such attribute.
|
||
ErrAttributeNotFound Error = "Attribute not found"
|
||
|
||
// ErrAttributeDecodeError means that agent is unable to decode value.
|
||
ErrAttributeDecodeError Error = "Attribute decode error"
|
||
|
||
// ErrErrorTooLong means that error reason is too long
|
||
ErrErrorTooLong Error = "Error reason is too long"
|
||
)
|