2020-04-25 02:36:33 +00:00
|
|
|
package udp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2020-07-11 19:50:32 +00:00
|
|
|
"context"
|
2020-04-25 02:36:33 +00:00
|
|
|
"encoding/binary"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"golang.org/x/crypto/nacl/secretbox"
|
2020-10-22 17:47:27 +00:00
|
|
|
"golang.org/x/time/rate"
|
2020-04-25 02:36:33 +00:00
|
|
|
)
|
|
|
|
|
2020-07-11 19:50:32 +00:00
|
|
|
// Dialer is the default dialer that this package uses for all its dialing.
|
|
|
|
var Dialer = net.Dialer{
|
|
|
|
Timeout: 10 * time.Second,
|
|
|
|
}
|
|
|
|
|
2020-11-17 19:34:22 +00:00
|
|
|
// Connection represents a voice connection. It is not thread-safe.
|
2020-04-25 02:36:33 +00:00
|
|
|
type Connection struct {
|
|
|
|
GatewayIP string
|
|
|
|
GatewayPort uint16
|
|
|
|
|
2020-10-22 17:47:27 +00:00
|
|
|
context context.Context
|
|
|
|
conn net.Conn
|
|
|
|
ssrc uint32
|
|
|
|
|
|
|
|
frequency rate.Limiter
|
|
|
|
packet [12]byte
|
|
|
|
secret [32]byte
|
2020-04-25 02:36:33 +00:00
|
|
|
|
|
|
|
sequence uint16
|
|
|
|
timestamp uint32
|
|
|
|
nonce [24]byte
|
|
|
|
}
|
|
|
|
|
2020-07-11 19:50:32 +00:00
|
|
|
func DialConnectionCtx(ctx context.Context, addr string, ssrc uint32) (*Connection, error) {
|
2020-04-25 02:36:33 +00:00
|
|
|
// Create a new UDP connection.
|
2020-07-11 19:50:32 +00:00
|
|
|
conn, err := Dialer.DialContext(ctx, "udp", addr)
|
2020-04-25 02:36:33 +00:00
|
|
|
if err != nil {
|
2020-05-16 21:14:49 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to dial host")
|
2020-04-25 02:36:33 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 17:16:42 +00:00
|
|
|
// https://discord.com/developers/docs/topics/voice-connections#ip-discovery
|
2020-04-25 02:36:33 +00:00
|
|
|
ssrcBuffer := [70]byte{
|
|
|
|
0x1, 0x2,
|
|
|
|
}
|
|
|
|
binary.BigEndian.PutUint16(ssrcBuffer[2:4], 70)
|
|
|
|
binary.BigEndian.PutUint32(ssrcBuffer[4:8], ssrc)
|
|
|
|
|
|
|
|
_, err = conn.Write(ssrcBuffer[:])
|
|
|
|
if err != nil {
|
2020-05-16 21:14:49 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to write SSRC buffer")
|
2020-04-25 02:36:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var ipBuffer [70]byte
|
|
|
|
|
|
|
|
// ReadFull makes sure to read all 70 bytes.
|
|
|
|
_, err = io.ReadFull(conn, ipBuffer[:])
|
|
|
|
if err != nil {
|
2020-05-16 21:14:49 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to read IP buffer")
|
2020-04-25 02:36:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ipbody := ipBuffer[4:68]
|
|
|
|
|
|
|
|
nullPos := bytes.Index(ipbody, []byte{'\x00'})
|
|
|
|
if nullPos < 0 {
|
|
|
|
return nil, errors.New("UDP IP discovery did not contain a null terminator")
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := ipbody[:nullPos]
|
|
|
|
port := binary.LittleEndian.Uint16(ipBuffer[68:70])
|
|
|
|
|
2020-11-03 17:16:42 +00:00
|
|
|
// https://discord.com/developers/docs/topics/voice-connections#encrypting-and-sending-voice
|
2020-04-25 03:14:06 +00:00
|
|
|
packet := [12]byte{
|
2020-04-25 02:36:33 +00:00
|
|
|
0: 0x80, // Version + Flags
|
|
|
|
1: 0x78, // Payload Type
|
|
|
|
// [2:4] // Sequence
|
|
|
|
// [4:8] // Timestamp
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write SSRC to the header.
|
2020-10-22 17:47:27 +00:00
|
|
|
binary.BigEndian.PutUint32(packet[8:12], ssrc) // SSRC
|
2020-04-25 02:36:33 +00:00
|
|
|
|
2020-10-22 17:47:27 +00:00
|
|
|
return &Connection{
|
|
|
|
GatewayIP: string(ip),
|
|
|
|
GatewayPort: port,
|
|
|
|
// 50 sends per second, 960 samples each at 48kHz
|
|
|
|
frequency: *rate.NewLimiter(rate.Every(20*time.Millisecond), 1),
|
|
|
|
context: context.Background(),
|
|
|
|
packet: packet,
|
|
|
|
ssrc: ssrc,
|
|
|
|
conn: conn,
|
|
|
|
}, nil
|
|
|
|
}
|
2020-04-25 02:36:33 +00:00
|
|
|
|
2020-10-22 17:47:27 +00:00
|
|
|
// UseSecret uses the given secret. This method is not thread-safe, so it should
|
|
|
|
// only be used right after initialization.
|
|
|
|
func (c *Connection) UseSecret(secret [32]byte) {
|
|
|
|
c.secret = secret
|
|
|
|
}
|
2020-04-25 02:36:33 +00:00
|
|
|
|
2020-10-22 17:47:27 +00:00
|
|
|
// UseContext lets the connection use the given context for its Write method.
|
|
|
|
// WriteCtx will override this context.
|
|
|
|
func (c *Connection) UseContext(ctx context.Context) error {
|
|
|
|
return c.useContext(ctx)
|
|
|
|
}
|
2020-04-25 02:36:33 +00:00
|
|
|
|
2020-10-22 17:47:27 +00:00
|
|
|
func (c *Connection) useContext(ctx context.Context) error {
|
|
|
|
if c.context == ctx {
|
|
|
|
return nil
|
|
|
|
}
|
2020-05-20 22:05:39 +00:00
|
|
|
|
2020-10-22 17:47:27 +00:00
|
|
|
c.context = ctx
|
2020-04-25 02:36:33 +00:00
|
|
|
|
2020-10-22 17:47:27 +00:00
|
|
|
if deadline, ok := c.context.Deadline(); ok {
|
|
|
|
return c.conn.SetWriteDeadline(deadline)
|
|
|
|
} else {
|
|
|
|
return c.conn.SetWriteDeadline(time.Time{})
|
2020-04-25 02:36:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Connection) Close() error {
|
2020-11-17 19:34:22 +00:00
|
|
|
return c.conn.Close()
|
2020-04-25 02:36:33 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 20:09:15 +00:00
|
|
|
// Write sends bytes into the voice UDP connection using the preset context.
|
2020-04-25 02:36:33 +00:00
|
|
|
func (c *Connection) Write(b []byte) (int, error) {
|
2020-10-22 17:47:27 +00:00
|
|
|
return c.write(b)
|
2020-07-11 19:50:32 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 20:09:15 +00:00
|
|
|
// WriteCtx sends bytes into the voice UDP connection with a timeout using the
|
|
|
|
// given context. It ignores the context inside the connection, but will restore
|
|
|
|
// the deadline after this call is done.
|
2020-07-11 19:50:32 +00:00
|
|
|
func (c *Connection) WriteCtx(ctx context.Context, b []byte) (int, error) {
|
2020-11-17 20:09:15 +00:00
|
|
|
if deadline, ok := ctx.Deadline(); ok {
|
|
|
|
ctx := c.context
|
|
|
|
defer c.useContext(ctx) // restore after we're done
|
|
|
|
|
|
|
|
c.conn.SetWriteDeadline(deadline)
|
2020-10-22 17:47:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return c.write(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Connection) write(b []byte) (int, error) {
|
|
|
|
// Write a new sequence.
|
|
|
|
binary.BigEndian.PutUint16(c.packet[2:4], c.sequence)
|
|
|
|
c.sequence++
|
|
|
|
|
|
|
|
binary.BigEndian.PutUint32(c.packet[4:8], c.timestamp)
|
|
|
|
c.timestamp += 960 // Samples
|
|
|
|
|
|
|
|
copy(c.nonce[:], c.packet[:])
|
|
|
|
|
|
|
|
if err := c.frequency.Wait(c.context); err != nil {
|
|
|
|
return 0, errors.Wrap(err, "failed to wait for frequency tick")
|
2020-04-25 02:36:33 +00:00
|
|
|
}
|
2020-10-22 17:47:27 +00:00
|
|
|
|
|
|
|
toSend := secretbox.Seal(c.packet[:], b, &c.nonce, &c.secret)
|
|
|
|
|
|
|
|
n, err := c.conn.Write(toSend)
|
|
|
|
if err != nil {
|
|
|
|
return n, errors.Wrap(err, "failed to write to UDP connection")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're not really returning everything, since we're "sealing" the bytes.
|
|
|
|
return len(b), nil
|
2020-04-25 02:36:33 +00:00
|
|
|
}
|