github.com/diamondburned/arikawa@v1.3.14/voice/udp/udp.go (about) 1 package udp 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/binary" 7 "io" 8 "net" 9 "time" 10 11 "github.com/pkg/errors" 12 "golang.org/x/crypto/nacl/secretbox" 13 "golang.org/x/time/rate" 14 ) 15 16 // Dialer is the default dialer that this package uses for all its dialing. 17 var Dialer = net.Dialer{ 18 Timeout: 10 * time.Second, 19 } 20 21 // ErrClosed is returned if a Write was called on a closed connection. 22 var ErrClosed = errors.New("UDP connection closed") 23 24 type Connection struct { 25 GatewayIP string 26 GatewayPort uint16 27 28 mutex chan struct{} // for ctx 29 30 context context.Context 31 conn net.Conn 32 ssrc uint32 33 34 frequency rate.Limiter 35 packet [12]byte 36 secret [32]byte 37 38 sequence uint16 39 timestamp uint32 40 nonce [24]byte 41 } 42 43 func DialConnectionCtx(ctx context.Context, addr string, ssrc uint32) (*Connection, error) { 44 // Create a new UDP connection. 45 conn, err := Dialer.DialContext(ctx, "udp", addr) 46 if err != nil { 47 return nil, errors.Wrap(err, "failed to dial host") 48 } 49 50 // https://discordapp.com/developers/docs/topics/voice-connections#ip-discovery 51 ssrcBuffer := [70]byte{ 52 0x1, 0x2, 53 } 54 binary.BigEndian.PutUint16(ssrcBuffer[2:4], 70) 55 binary.BigEndian.PutUint32(ssrcBuffer[4:8], ssrc) 56 57 _, err = conn.Write(ssrcBuffer[:]) 58 if err != nil { 59 return nil, errors.Wrap(err, "failed to write SSRC buffer") 60 } 61 62 var ipBuffer [70]byte 63 64 // ReadFull makes sure to read all 70 bytes. 65 _, err = io.ReadFull(conn, ipBuffer[:]) 66 if err != nil { 67 return nil, errors.Wrap(err, "failed to read IP buffer") 68 } 69 70 ipbody := ipBuffer[4:68] 71 72 nullPos := bytes.Index(ipbody, []byte{'\x00'}) 73 if nullPos < 0 { 74 return nil, errors.New("UDP IP discovery did not contain a null terminator") 75 } 76 77 ip := ipbody[:nullPos] 78 port := binary.LittleEndian.Uint16(ipBuffer[68:70]) 79 80 // https://discordapp.com/developers/docs/topics/voice-connections#encrypting-and-sending-voice 81 packet := [12]byte{ 82 0: 0x80, // Version + Flags 83 1: 0x78, // Payload Type 84 // [2:4] // Sequence 85 // [4:8] // Timestamp 86 } 87 88 // Write SSRC to the header. 89 binary.BigEndian.PutUint32(packet[8:12], ssrc) // SSRC 90 91 return &Connection{ 92 GatewayIP: string(ip), 93 GatewayPort: port, 94 // 50 sends per second, 960 samples each at 48kHz 95 frequency: *rate.NewLimiter(rate.Every(20*time.Millisecond), 1), 96 context: context.Background(), 97 mutex: make(chan struct{}, 1), 98 packet: packet, 99 ssrc: ssrc, 100 conn: conn, 101 }, nil 102 } 103 104 // UseSecret uses the given secret. This method is not thread-safe, so it should 105 // only be used right after initialization. 106 func (c *Connection) UseSecret(secret [32]byte) { 107 c.secret = secret 108 } 109 110 // UseContext lets the connection use the given context for its Write method. 111 // WriteCtx will override this context. 112 func (c *Connection) UseContext(ctx context.Context) error { 113 c.mutex <- struct{}{} 114 defer func() { <-c.mutex }() 115 116 return c.useContext(ctx) 117 } 118 119 func (c *Connection) useContext(ctx context.Context) error { 120 if c.conn == nil { 121 return ErrClosed 122 } 123 124 if c.context == ctx { 125 return nil 126 } 127 128 c.context = ctx 129 130 if deadline, ok := c.context.Deadline(); ok { 131 return c.conn.SetWriteDeadline(deadline) 132 } else { 133 return c.conn.SetWriteDeadline(time.Time{}) 134 } 135 } 136 137 func (c *Connection) Close() error { 138 c.mutex <- struct{}{} 139 err := c.conn.Close() 140 c.conn = nil 141 <-c.mutex 142 return err 143 } 144 145 // Write sends bytes into the voice UDP connection. 146 func (c *Connection) Write(b []byte) (int, error) { 147 select { 148 case c.mutex <- struct{}{}: 149 defer func() { <-c.mutex }() 150 case <-c.context.Done(): 151 return 0, c.context.Err() 152 } 153 154 if c.conn == nil { 155 return 0, ErrClosed 156 } 157 158 return c.write(b) 159 } 160 161 // WriteCtx sends bytes into the voice UDP connection with a timeout. 162 func (c *Connection) WriteCtx(ctx context.Context, b []byte) (int, error) { 163 select { 164 case c.mutex <- struct{}{}: 165 defer func() { <-c.mutex }() 166 case <-c.context.Done(): 167 return 0, c.context.Err() 168 case <-ctx.Done(): 169 return 0, ctx.Err() 170 } 171 172 if err := c.useContext(ctx); err != nil { 173 return 0, errors.Wrap(err, "failed to use context") 174 } 175 176 return c.write(b) 177 } 178 179 // write is thread-unsafe. 180 func (c *Connection) write(b []byte) (int, error) { 181 // Write a new sequence. 182 binary.BigEndian.PutUint16(c.packet[2:4], c.sequence) 183 c.sequence++ 184 185 binary.BigEndian.PutUint32(c.packet[4:8], c.timestamp) 186 c.timestamp += 960 // Samples 187 188 copy(c.nonce[:], c.packet[:]) 189 190 if err := c.frequency.Wait(c.context); err != nil { 191 return 0, errors.Wrap(err, "failed to wait for frequency tick") 192 } 193 194 toSend := secretbox.Seal(c.packet[:], b, &c.nonce, &c.secret) 195 196 n, err := c.conn.Write(toSend) 197 if err != nil { 198 return n, errors.Wrap(err, "failed to write to UDP connection") 199 } 200 201 // We're not really returning everything, since we're "sealing" the bytes. 202 return len(b), nil 203 }