import gevent import socket import struct import time import nacl.secret from holster.enum import Enum from holster.emitter import Emitter from disco.gateway.encoding.json import JSONEncoder from disco.util.websocket import Websocket from disco.util.logging import LoggingClass from disco.voice.packets import VoiceOPCode from disco.gateway.packets import OPCode VoiceState = Enum( DISCONNECTED=0, AWAITING_ENDPOINT=1, AUTHENTICATING=2, CONNECTING=3, CONNECTED=4, VOICE_CONNECTING=5, VOICE_CONNECTED=6, ) class VoiceException(Exception): def __init__(self, msg, client): self.voice_client = client super(VoiceException, self).__init__(msg) class UDPVoiceClient(LoggingClass): def __init__(self, vc): super(UDPVoiceClient, self).__init__() self.vc = vc # The underlying UDP socket self.conn = None # Connection information self.ip = None self.port = None self.run_task = None self.connected = False def send_frame(self, frame, sequence=None, timestamp=None): # Convert the frame to a bytearray frame = bytearray(frame) # First, pack the header (TODO: reuse bytearray?) header = bytearray(24) header[0] = 0x80 header[1] = 0x78 struct.pack_into('>H', header, 2, sequence or self.vc.sequence) struct.pack_into('>I', header, 4, timestamp or self.vc.timestamp) struct.pack_into('>i', header, 8, self.vc.ssrc) # Now encrypt the payload with the nonce as a header raw = self.vc.secret_box.encrypt(bytes(frame), bytes(header)).ciphertext # Send the header (sans nonce padding) plus the payload self.send(header[:12] + raw) # Increment our sequence counter self.vc.sequence += 1 if self.vc.sequence >= 65535: self.vc.sequence = 0 def run(self): while True: self.conn.recvfrom(4096) def send(self, data): self.conn.sendto(data, (self.ip, self.port)) def disconnect(self): self.run_task.kill() def connect(self, host, port, timeout=10, addrinfo=None): self.ip = socket.gethostbyname(host) self.port = port self.conn = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) if addrinfo: ip, port = addrinfo else: # Send discovery packet packet = bytearray(70) struct.pack_into('>I', packet, 0, self.vc.ssrc) self.send(packet) # Wait for a response try: data, addr = gevent.spawn(lambda: self.conn.recvfrom(70)).get(timeout=timeout) except gevent.Timeout: return (None, None) # Read IP and port ip = str(data[4:]).split('\x00', 1)[0] port = struct.unpack('