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.

encrypted?[RW]

@return [true, false] whether or not UDP communications are encrypted.

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 41
def initialize
  @socket = UDPSocket.new
end

Public Instance Methods

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

Initializes the UDP socket with data obtained from opcode 2. @param endpoint [String] The voice endpoint 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 50
def connect(endpoint, port, ssrc)
  @endpoint = endpoint
  @endpoint = @endpoint[6..-1] if @endpoint.start_with? 'wss://'
  @endpoint = @endpoint.gsub(':80', '') # The endpoint may contain a port, we don't want that
  @endpoint = Resolv.getaddress @endpoint

  @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 62
def receive_discovery_reply
  # Wait for a UDP message
  message = @socket.recv(70)
  ip = message[4..-3].delete("\0")
  port = message[-2..-1].to_i
  [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 75
def send_audio(buf, sequence, time)
  # Header of the audio packet
  header = [0x80, 0x78, sequence, time, @ssrc].pack('CCnNN')

  # Encrypt data, if necessary
  buf = encrypt_audio(header, buf) if encrypted?

  send_packet(header + buf)
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 87
def send_discovery
  discovery_packet = [@ssrc].pack('N')

  # Add 66 zeroes so the packet is 70 bytes long
  discovery_packet += "\0" * 66
  send_packet(discovery_packet)
end

Private Instance Methods

encrypt_audio(header, buf) click to toggle source

Encrypts audio data using RbNaCl @param header [String] The header of the packet, to be used as the nonce @param buf [String] The encoded audio data to be encrypted @return [String] the audio data, encrypted

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

  box = RbNaCl::SecretBox.new(@secret_key)

  # The nonce is the header of the voice packet with 12 null bytes appended
  nonce = header + ([0] * 12).pack('C*')

  box.encrypt(nonce, buf)
end
send_packet(packet) click to toggle source
# File lib/discordrb/voice/network.rb, line 112
def send_packet(packet)
  @socket.send(packet, 0, @endpoint, @port)
end