class Discordrb::Voice::VoiceUDP

Represents a UDP connection to a voice server. This connection is used to send the actual audio data.

Attributes

encrypted[RW]

@return [true, false] whether or not UDP communications are encrypted. @deprecated Discord no longer supports unencrypted voice communication.

encrypted?[RW]

@return [true, false] whether or not UDP communications are encrypted. @deprecated Discord no longer supports unencrypted voice communication.

mode[RW]

The UDP encryption mode

secret_key[W]

Sets the secret key used for encryption

Public Class Methods

new() click to toggle source

Creates a new UDP connection. Only creates a socket as the discovery reply may come before the data is initialized.

# File lib/discordrb/voice/network.rb, line 52
def initialize
  @socket = UDPSocket.new
  @encrypted = true
end

Public Instance Methods

connect(ip, port, ssrc) click to toggle source

Initializes the UDP socket with data obtained from opcode 2. @param ip [String] The IP address to connect to. @param port [Integer] The port to connect to. @param ssrc [Integer] The Super Secret Relay Code (SSRC). Discord uses this to identify different voice users

on the same endpoint.
# File lib/discordrb/voice/network.rb, line 62
def connect(ip, port, ssrc)
  @ip = ip
  @port = port
  @ssrc = ssrc
end
receive_discovery_reply() click to toggle source

Waits for a UDP discovery reply, and returns the sent data. @return [Array(String, Integer)] the IP and port received from the discovery reply.

# File lib/discordrb/voice/network.rb, line 70
def receive_discovery_reply
  # Wait for a UDP message
  message = @socket.recv(74)
  ip = message[8..-3].delete("\0")
  port = message[-2..].unpack1('n')
  [ip, port]
end
send_audio(buf, sequence, time) click to toggle source

Makes an audio packet from a buffer and sends it to Discord. @param buf [String] The audio data to send, must be exactly one Opus frame @param sequence [Integer] The packet sequence number, incremented by one for subsequent packets @param time [Integer] When this packet should be played back, in no particular unit (essentially just the

sequence number multiplied by 960)
# File lib/discordrb/voice/network.rb, line 83
def send_audio(buf, sequence, time)
  # Header of the audio packet
  header = [0x80, 0x78, sequence, time, @ssrc].pack('CCnNN')

  nonce = generate_nonce(header)
  buf = encrypt_audio(buf, nonce)

  data = header + buf

  # xsalsa20_poly1305 does not require an appended nonce
  data += nonce unless @mode == 'xsalsa20_poly1305'

  send_packet(data)
end
send_discovery() click to toggle source

Sends the UDP discovery packet with the internally stored SSRC. Discord will send a reply afterwards which can be received using {#receive_discovery_reply}

# File lib/discordrb/voice/network.rb, line 100
def send_discovery
  # Create empty packet
  discovery_packet = ''

  # Add Type request (0x1 = request, 0x2 = response)
  discovery_packet += [0x1].pack('n')

  # Add Length (excluding Type and itself = 70)
  discovery_packet += [70].pack('n')

  # Add SSRC
  discovery_packet += [@ssrc].pack('N')

  # Add 66 zeroes so the packet is 74 bytes long
  discovery_packet += "\0" * 66

  send_packet(discovery_packet)
end

Private Instance Methods

encrypt_audio(buf, nonce) click to toggle source

Encrypts audio data using libsodium @param buf [String] The encoded audio data to be encrypted @param nonce [String] The nonce to be used to encrypt the data @return [String] the audio data, encrypted

# File lib/discordrb/voice/network.rb, line 125
def encrypt_audio(buf, nonce)
  raise 'No secret key found, despite encryption being enabled!' unless @secret_key

  secret_box = Discordrb::Voice::SecretBox.new(@secret_key)

  # Nonces must be 24 bytes in length. We right pad with null bytes for poly1305 and poly1305_lite
  secret_box.box(nonce.ljust(24, "\0"), buf)
end
generate_nonce(header) click to toggle source

@param header [String] The header of the packet, to be used as the nonce @return [String] @note

The nonce generated depends on the encryption mode.
In xsalsa20_poly1305 the nonce is the header plus twelve null bytes for padding.
In xsalsa20_poly1305_suffix, the nonce is 24 random bytes
In xsalsa20_poly1305_lite, the nonce is an incremental 4 byte int.
# File lib/discordrb/voice/network.rb, line 145
def generate_nonce(header)
  case @mode
  when 'xsalsa20_poly1305'
    header
  when 'xsalsa20_poly1305_suffix'
    Random.urandom(24)
  when 'xsalsa20_poly1305_lite'
    case @lite_nonce
    when nil, 0xff_ff_ff_ff
      @lite_nonce = 0
    else
      @lite_nonce += 1
    end
    [@lite_nonce].pack('N')
  else
    raise "`#{@mode}' is not a supported encryption mode"
  end
end
send_packet(packet) click to toggle source
# File lib/discordrb/voice/network.rb, line 134
def send_packet(packet)
  @socket.send(packet, 0, @ip, @port)
end