discordrb 3.6.1 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -298,9 +298,7 @@ module Discordrb
298
298
  send_identify(@token, {
299
299
  os: RUBY_PLATFORM,
300
300
  browser: 'discordrb',
301
- device: 'discordrb',
302
- referrer: '',
303
- referring_domain: ''
301
+ device: 'discordrb'
304
302
  }, compress, LARGE_THRESHOLD, @shard_key, @intents)
305
303
  end
306
304
 
@@ -677,9 +675,8 @@ module Discordrb
677
675
  LOGGER.log_exception(e)
678
676
  end
679
677
 
680
- # rubocop:disable Lint/UselessConstantScoping
681
678
  ZLIB_SUFFIX = "\x00\x00\xFF\xFF".b.freeze
682
- # rubocop:enable Lint/UselessConstantScoping
679
+ private_constant :ZLIB_SUFFIX
683
680
 
684
681
  def handle_message(msg)
685
682
  case @compress_mode
@@ -10,7 +10,7 @@ module Discordrb
10
10
 
11
11
  # ID based comparison
12
12
  def ==(other)
13
- Discordrb.id_compare(@id, other)
13
+ Discordrb.id_compare?(@id, other)
14
14
  end
15
15
 
16
16
  alias_method :eql?, :==
@@ -145,9 +145,8 @@ module Discordrb
145
145
 
146
146
  # Comparison based on permission bits
147
147
  def ==(other)
148
- # rubocop:disable Lint/Void
149
- false unless other.is_a? Discordrb::Permissions
150
- # rubocop:enable Lint/Void
148
+ return false unless other.is_a?(Discordrb::Permissions)
149
+
151
150
  bits == other.bits
152
151
  end
153
152
  end
@@ -3,5 +3,5 @@
3
3
  # Discordrb and all its functionality, in this case only the version.
4
4
  module Discordrb
5
5
  # The current version of discordrb.
6
- VERSION = '3.6.1'
6
+ VERSION = '3.7.0'
7
7
  end
@@ -5,6 +5,7 @@ require 'socket'
5
5
  require 'json'
6
6
 
7
7
  require 'discordrb/websocket'
8
+ require 'discordrb/voice/opcodes'
8
9
 
9
10
  begin
10
11
  LIBSODIUM_AVAILABLE = if ENV['DISCORDRB_NONACL']
@@ -22,14 +23,14 @@ module Discordrb::Voice
22
23
  # Signifies to Discord that encryption should be used
23
24
  # @deprecated Discord now supports multiple encryption options.
24
25
  # TODO: Resolve replacement for this constant.
25
- ENCRYPTED_MODE = 'xsalsa20_poly1305'
26
+ ENCRYPTED_MODE = 'aead_xchacha20_poly1305_rtpsize'
26
27
 
27
28
  # Signifies to Discord that no encryption should be used
28
29
  # @deprecated Discord no longer supports unencrypted voice communication.
29
30
  PLAIN_MODE = 'plain'
30
31
 
31
32
  # Encryption modes supported by Discord
32
- ENCRYPTION_MODES = %w[xsalsa20_poly1305_lite xsalsa20_poly1305_suffix xsalsa20_poly1305].freeze
33
+ ENCRYPTION_MODES = %w[aead_xchacha20_poly1305_rtpsize].freeze
33
34
 
34
35
  # Represents a UDP connection to a voice server. This connection is used to send the actual audio data.
35
36
  class VoiceUDP
@@ -82,15 +83,11 @@ module Discordrb::Voice
82
83
  # sequence number multiplied by 960)
83
84
  def send_audio(buf, sequence, time)
84
85
  # Header of the audio packet
85
- header = [0x80, 0x78, sequence, time, @ssrc].pack('CCnNN')
86
+ header = generate_header(sequence, time)
86
87
 
87
- nonce = generate_nonce(header)
88
- buf = encrypt_audio(buf, nonce)
89
-
90
- data = header + buf
91
-
92
- # xsalsa20_poly1305 does not require an appended nonce
93
- data += nonce unless @mode == 'xsalsa20_poly1305'
88
+ nonce = generate_nonce
89
+ buf = encrypt_audio(buf, header, nonce)
90
+ data = header + buf + nonce.byteslice(0, 4)
94
91
 
95
92
  send_packet(data)
96
93
  end
@@ -120,46 +117,44 @@ module Discordrb::Voice
120
117
 
121
118
  # Encrypts audio data using libsodium
122
119
  # @param buf [String] The encoded audio data to be encrypted
120
+ # @param header [String] The RTP header of the packet, used as associated data
123
121
  # @param nonce [String] The nonce to be used to encrypt the data
124
122
  # @return [String] the audio data, encrypted
125
- def encrypt_audio(buf, nonce)
123
+ def encrypt_audio(buf, header, nonce)
126
124
  raise 'No secret key found, despite encryption being enabled!' unless @secret_key
127
125
 
128
- secret_box = Discordrb::Voice::SecretBox.new(@secret_key)
129
-
130
- # Nonces must be 24 bytes in length. We right pad with null bytes for poly1305 and poly1305_lite
131
- secret_box.box(nonce.ljust(24, "\0"), buf)
126
+ case @mode
127
+ when 'aead_xchacha20_poly1305_rtpsize'
128
+ Discordrb::Voice::XChaCha20AEAD.encrypt(buf, header, nonce, @secret_key)
129
+ else
130
+ raise "`#{@mode}' is not a supported encryption mode"
131
+ end
132
132
  end
133
133
 
134
134
  def send_packet(packet)
135
135
  @socket.send(packet, 0, @ip, @port)
136
136
  end
137
137
 
138
- # @param header [String] The header of the packet, to be used as the nonce
139
138
  # @return [String]
140
- # @note
141
- # The nonce generated depends on the encryption mode.
142
- # In xsalsa20_poly1305 the nonce is the header plus twelve null bytes for padding.
143
- # In xsalsa20_poly1305_suffix, the nonce is 24 random bytes
144
- # In xsalsa20_poly1305_lite, the nonce is an incremental 4 byte int.
145
- def generate_nonce(header)
139
+ def generate_nonce
146
140
  case @mode
147
- when 'xsalsa20_poly1305'
148
- header
149
- when 'xsalsa20_poly1305_suffix'
150
- Random.urandom(24)
151
- when 'xsalsa20_poly1305_lite'
152
- case @lite_nonce
141
+ when 'aead_xchacha20_poly1305_rtpsize'
142
+ case @incremental_nonce
153
143
  when nil, 0xff_ff_ff_ff
154
- @lite_nonce = 0
144
+ @incremental_nonce = 0
155
145
  else
156
- @lite_nonce += 1
146
+ @incremental_nonce += 1
157
147
  end
158
- [@lite_nonce].pack('N')
148
+ [@incremental_nonce].pack('N').ljust(24, "\0")
159
149
  else
160
150
  raise "`#{@mode}' is not a supported encryption mode"
161
151
  end
162
152
  end
153
+
154
+ # @return [String]
155
+ def generate_header(sequence, time)
156
+ [0x80, 0x78, sequence, time, @ssrc].pack('CCnNN')
157
+ end
163
158
  end
164
159
 
165
160
  # Represents a websocket client connection to the voice server. The websocket connection (sometimes called vWS) is
@@ -167,7 +162,7 @@ module Discordrb::Voice
167
162
  # circle around users on Discord, and obtaining UDP connection info.
168
163
  class VoiceWS
169
164
  # The version of the voice gateway that's supposed to be used.
170
- VOICE_GATEWAY_VERSION = 4
165
+ VOICE_GATEWAY_VERSION = 8
171
166
 
172
167
  # @return [VoiceUDP] the UDP voice connection over which the actual audio data is sent.
173
168
  attr_reader :udp
@@ -186,7 +181,7 @@ module Discordrb::Voice
186
181
  @token = token
187
182
  @session = session
188
183
 
189
- @endpoint = endpoint.split(':').first
184
+ @endpoint = endpoint
190
185
 
191
186
  @udp = VoiceUDP.new
192
187
  end
@@ -197,15 +192,15 @@ module Discordrb::Voice
197
192
  # @param session_id [String] The voice session ID
198
193
  # @param token [String] The Discord authentication token
199
194
  def send_init(server_id, bot_user_id, session_id, token)
200
- @client.send({
201
- op: 0,
202
- d: {
195
+ send_opcode(
196
+ Opcodes::IDENTIFY,
197
+ {
203
198
  server_id: server_id,
204
199
  user_id: bot_user_id,
205
200
  session_id: session_id,
206
201
  token: token
207
202
  }
208
- }.to_json)
203
+ )
209
204
  end
210
205
 
211
206
  # Sends the UDP connection packet (op 1)
@@ -213,9 +208,9 @@ module Discordrb::Voice
213
208
  # @param port [Integer] The port to bind UDP to
214
209
  # @param mode [Object] Which mode to use for the voice connection
215
210
  def send_udp_connection(ip, port, mode)
216
- @client.send({
217
- op: 1,
218
- d: {
211
+ send_opcode(
212
+ Opcodes::SELECT_PROTOCOL,
213
+ {
219
214
  protocol: 'udp',
220
215
  data: {
221
216
  address: ip,
@@ -223,7 +218,7 @@ module Discordrb::Voice
223
218
  mode: mode
224
219
  }
225
220
  }
226
- }.to_json)
221
+ )
227
222
  end
228
223
 
229
224
  # Send a heartbeat (op 3), has to be done every @heartbeat_interval seconds or the connection will terminate
@@ -231,22 +226,33 @@ module Discordrb::Voice
231
226
  millis = Time.now.strftime('%s%L').to_i
232
227
  @bot.debug("Sending voice heartbeat at #{millis}")
233
228
 
234
- @client.send({
235
- op: 3,
236
- d: millis
237
- }.to_json)
229
+ send_opcode(
230
+ Opcodes::HEARTBEAT,
231
+ {
232
+ t: millis,
233
+ seq_ack: @seq
234
+ }
235
+ )
238
236
  end
239
237
 
240
238
  # Send a speaking packet (op 5). This determines the green circle around the avatar in the voice channel
241
239
  # @param value [true, false, Integer] Whether or not the bot should be speaking, can also be a bitmask denoting audio type.
242
240
  def send_speaking(value)
243
241
  @bot.debug("Speaking: #{value}")
244
- @client.send({
245
- op: 5,
246
- d: {
242
+ send_opcode(
243
+ Opcodes::SPEAKING,
244
+ {
247
245
  speaking: value,
248
246
  delay: 0
249
247
  }
248
+ )
249
+ end
250
+
251
+ def send_opcode(opcode, data)
252
+ @bot.debug("Sending voice opcode #{opcode} with data: #{data}")
253
+ @client.send({
254
+ op: opcode,
255
+ d: data
250
256
  }.to_json)
251
257
  end
252
258
 
@@ -265,8 +271,10 @@ module Discordrb::Voice
265
271
  @bot.debug("Received VWS message! #{msg}")
266
272
  packet = JSON.parse(msg)
267
273
 
274
+ @seq = packet['seq'] if packet['seq']
275
+
268
276
  case packet['op']
269
- when 2
277
+ when Discordrb::Voice::Opcodes::READY
270
278
  # Opcode 2 contains data to initialize the UDP connection
271
279
  @ws_data = packet['d']
272
280
 
@@ -277,16 +285,20 @@ module Discordrb::Voice
277
285
 
278
286
  @udp.connect(@ws_data['ip'], @port, @ssrc)
279
287
  @udp.send_discovery
280
- when 4
288
+ when Discordrb::Voice::Opcodes::SESSION_DESCRIPTION
281
289
  # Opcode 4 sends the secret key used for encryption
282
290
  @ws_data = packet['d']
283
291
 
292
+ # Reset the sequence when starting a new session
293
+ @seq = 0
294
+
284
295
  @ready = true
285
296
  @udp.secret_key = @ws_data['secret_key'].pack('C*')
286
297
  @udp.mode = @ws_data['mode']
287
- when 8
298
+ when Discordrb::Voice::Opcodes::HELLO
288
299
  # Opcode 8 contains the heartbeat interval.
289
300
  @heartbeat_interval = packet['d']['heartbeat_interval']
301
+ send_heartbeat
290
302
  end
291
303
  end
292
304
 
@@ -347,7 +359,7 @@ module Discordrb::Voice
347
359
  end
348
360
 
349
361
  def init_ws
350
- host = "wss://#{@endpoint}:443/?v=#{VOICE_GATEWAY_VERSION}"
362
+ host = "wss://#{@endpoint}/?v=#{VOICE_GATEWAY_VERSION}"
351
363
  @bot.debug("Connecting VWS to host: #{host}")
352
364
 
353
365
  # Connect the WS
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Voice websocket opcodes
4
+ # @!visibility private
5
+ module Discordrb::Voice::Opcodes
6
+ IDENTIFY = 0
7
+ SELECT_PROTOCOL = 1
8
+ READY = 2
9
+ HEARTBEAT = 3
10
+ SESSION_DESCRIPTION = 4
11
+ SPEAKING = 5
12
+ HEARTBEAT_ACK = 6
13
+ RESUME = 7
14
+ HELLO = 8
15
+ RESUMED = 9
16
+ CLIENT_CONNECT = 10
17
+ CLIENT_DISCONNECT = 11
18
+ DAVE_PREPARE_TRANSITION = 21
19
+ DAVE_EXECUTE_TRANSITION = 22
20
+ DAVE_TRANSITION_READY = 23
21
+ DAVE_PREPARE_EPOCH = 24
22
+ DAVE_MLS_EXTERNAL_SENDER = 25
23
+ DAVE_MLS_KEY_PACKAGE = 26
24
+ DAVE_MLS_PROPOSALS = 27
25
+ DAVE_MLS_COMMIT_WELCOME = 28
26
+ DAVE_MLS_ANNOUNCE_COMMIT_TRANSITION = 29
27
+ DAVE_MLS_WELCOME = 30
28
+ DAVE_MLS_INVALID_COMMIT_WELCOME = 31
29
+ end
@@ -1,98 +1,157 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'ffi'
4
+ require 'securerandom'
4
5
 
6
+ # :nodoc:
5
7
  module Discordrb::Voice
6
8
  # @!visibility private
7
9
  module Sodium
8
- extend ::FFI::Library
9
-
10
- ffi_lib(['sodium', 'libsodium.so.18', 'libsodium.so.23'])
11
-
12
- # Encryption & decryption
13
- attach_function(:crypto_secretbox_xsalsa20poly1305, %i[pointer pointer ulong_long pointer pointer], :int)
14
- attach_function(:crypto_secretbox_xsalsa20poly1305_open, %i[pointer pointer ulong_long pointer pointer], :int)
15
-
16
- # Constants
17
- attach_function(:crypto_secretbox_xsalsa20poly1305_keybytes, [], :size_t)
18
- attach_function(:crypto_secretbox_xsalsa20poly1305_noncebytes, [], :size_t)
19
- attach_function(:crypto_secretbox_xsalsa20poly1305_zerobytes, [], :size_t)
20
- attach_function(:crypto_secretbox_xsalsa20poly1305_boxzerobytes, [], :size_t)
10
+ extend FFI::Library
11
+ ffi_lib 'sodium'
12
+
13
+ # @!group Constants
14
+
15
+ # Initializes libsodium
16
+ # @return [Integer] 0 on success
17
+ attach_function :sodium_init, [], :int
18
+
19
+ # Returns the key size (in bytes)
20
+ # @return [Integer]
21
+ attach_function :crypto_aead_xchacha20poly1305_ietf_keybytes, [], :size_t
22
+
23
+ # Returns the nonce size (in bytes)
24
+ # @return [Integer]
25
+ attach_function :crypto_aead_xchacha20poly1305_ietf_npubbytes, [], :size_t
26
+
27
+ # Returns the authentication tag size (in bytes)
28
+ # @return [Integer]
29
+ attach_function :crypto_aead_xchacha20poly1305_ietf_abytes, [], :size_t
30
+
31
+ # @!endgroup
32
+
33
+ # @!group AEAD Encrypt/Decrypt
34
+
35
+ # Performs authenticated encryption using XChaCha20-Poly1305
36
+ #
37
+ # @!macro [attach] crypto_aead_xchacha20poly1305_ietf_encrypt
38
+ # @param c [FFI::Pointer] output buffer for ciphertext
39
+ # @param clen_p [FFI::Pointer] output pointer for ciphertext length
40
+ # @param m [FFI::Pointer] input message pointer
41
+ # @param mlen [Integer] length of the message
42
+ # @param ad [FFI::Pointer] pointer to associated data
43
+ # @param adlen [Integer] length of associated data
44
+ # @param nsec [FFI::Pointer, nil] (not used, must be nil)
45
+ # @param npub [FFI::Pointer] nonce pointer
46
+ # @param k [FFI::Pointer] key pointer
47
+ # @return [Integer] 0 on success
48
+ attach_function :crypto_aead_xchacha20poly1305_ietf_encrypt, %i[
49
+ pointer pointer pointer ulong_long
50
+ pointer ulong_long
51
+ pointer pointer pointer
52
+ ], :int
53
+
54
+ # Decrypts XChaCha20-Poly1305 AEAD-encrypted data
55
+ # @!macro [attach] crypto_aead_xchacha20poly1305_ietf_decrypt
56
+ # @param m [FFI::Pointer] output buffer for decrypted message
57
+ # @param mlen_p [FFI::Pointer] output pointer for decrypted length
58
+ # @param nsec [FFI::Pointer, nil] (not used, must be nil)
59
+ # @param c [FFI::Pointer] ciphertext pointer
60
+ # @param clen [Integer] length of ciphertext
61
+ # @param ad [FFI::Pointer] pointer to associated data
62
+ # @param adlen [Integer] length of associated data
63
+ # @param npub [FFI::Pointer] nonce pointer
64
+ # @param k [FFI::Pointer] key pointer
65
+ # @return [Integer] 0 on success
66
+ attach_function :crypto_aead_xchacha20poly1305_ietf_decrypt, %i[
67
+ pointer pointer pointer pointer ulong_long
68
+ pointer ulong_long pointer pointer
69
+ ], :int
70
+
71
+ # @!endgroup
21
72
  end
22
73
 
23
- # Utility class for interacting with required `xsalsa20poly1305` functions for voice transmission
24
- # @!visibility private
25
- class SecretBox
26
- # Exception raised when a key or nonce with invalid length is used
27
- class LengthError < RuntimeError
28
- end
29
-
30
- # Exception raised when encryption or decryption fails
31
- class CryptoError < RuntimeError
32
- end
33
-
34
- # Required key length
35
- KEY_LENGTH = Sodium.crypto_secretbox_xsalsa20poly1305_keybytes
36
-
37
- # Required nonce length
38
- NONCE_BYTES = Sodium.crypto_secretbox_xsalsa20poly1305_noncebytes
39
-
40
- # Zero byte padding for encryption
41
- ZERO_BYTES = Sodium.crypto_secretbox_xsalsa20poly1305_zerobytes
42
-
43
- # Zero byte padding for decryption
44
- BOX_ZERO_BYTES = Sodium.crypto_secretbox_xsalsa20poly1305_boxzerobytes
74
+ Sodium.sodium_init
45
75
 
46
- # @param key [String] Crypto key of length {KEY_LENGTH}
47
- def initialize(key)
48
- raise(LengthError, 'Key length') if key.bytesize != KEY_LENGTH
76
+ # High-level wrapper class
77
+ class XChaCha20AEAD
78
+ KEY_BYTES = Sodium.crypto_aead_xchacha20poly1305_ietf_keybytes
79
+ NONCE_BYTES = Sodium.crypto_aead_xchacha20poly1305_ietf_npubbytes
80
+ TAG_BYTES = Sodium.crypto_aead_xchacha20poly1305_ietf_abytes
49
81
 
50
- @key = key
82
+ # Generates a random key
83
+ # @return [String] binary key
84
+ def self.generate_key
85
+ SecureRandom.random_bytes(KEY_BYTES)
51
86
  end
52
87
 
53
- # Encrypts a message using this box's key
54
- # @param nonce [String] encryption nonce for this message
55
- # @param message [String] message to be encrypted
56
- def box(nonce, message)
57
- raise(LengthError, 'Nonce length') if nonce.bytesize != NONCE_BYTES
58
-
59
- message_padded = prepend_zeroes(ZERO_BYTES, message)
60
- buffer = zero_string(message_padded.bytesize)
61
-
62
- success = Sodium.crypto_secretbox_xsalsa20poly1305(buffer, message_padded, message_padded.bytesize, nonce, @key)
63
- raise(CryptoError, "Encryption failed (#{success})") unless success.zero?
64
-
65
- remove_zeroes(BOX_ZERO_BYTES, buffer)
66
- end
67
-
68
- # Decrypts the given ciphertext using this box's key
69
- # @param nonce [String] encryption nonce for this ciphertext
70
- # @param ciphertext [String] ciphertext to decrypt
71
- def open(nonce, ciphertext)
72
- raise(LengthError, 'Nonce length') if nonce.bytesize != NONCE_BYTES
73
-
74
- ct_padded = prepend_zeroes(BOX_ZERO_BYTES, ciphertext)
75
- buffer = zero_string(ct_padded.bytesize)
76
-
77
- success = Sodium.crypto_secretbox_xsalsa20poly1305_open(buffer, ct_padded, ct_padded.bytesize, nonce, @key)
78
- raise(CryptoError, "Decryption failed (#{success})") unless success.zero?
79
-
80
- remove_zeroes(ZERO_BYTES, buffer)
81
- end
82
-
83
- private
84
-
85
- def zero_string(size)
86
- str = "\0" * size
87
- str.force_encoding('ASCII-8BIT') if str.respond_to?(:force_encoding)
88
+ # Generates a random nonce
89
+ # @return [String] binary nonce
90
+ def self.generate_nonce
91
+ SecureRandom.random_bytes(NONCE_BYTES)
88
92
  end
89
93
 
90
- def prepend_zeroes(size, string)
91
- zero_string(size) + string
94
+ # Encrypts a message using XChaCha20-Poly1305
95
+ #
96
+ # @param message [String] plaintext to encrypt
97
+ # @param key [String] 32-byte encryption key
98
+ # @param nonce [String] 24-byte nonce
99
+ # @param add [String] optional associated data
100
+ # @return [String] ciphertext (includes the auth tag)
101
+ def self.encrypt(message, add, nonce, key)
102
+ raise ArgumentError, 'Invalid key size' unless key.bytesize == KEY_BYTES
103
+ raise ArgumentError, 'Invalid nonce size' unless nonce.bytesize == NONCE_BYTES
104
+
105
+ message_ptr = FFI::MemoryPointer.from_string(message)
106
+ ad_ptr = FFI::MemoryPointer.from_string(add)
107
+
108
+ c_len = message.bytesize + TAG_BYTES
109
+ ciphertext = FFI::MemoryPointer.new(:uchar, c_len)
110
+ clen_p = FFI::MemoryPointer.new(:ulong_long)
111
+
112
+ result = Sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
113
+ ciphertext, clen_p,
114
+ message_ptr, message.bytesize,
115
+ ad_ptr, add.bytesize,
116
+ nil,
117
+ FFI::MemoryPointer.from_string(nonce),
118
+ FFI::MemoryPointer.from_string(key)
119
+ )
120
+
121
+ raise 'Encryption failed' unless result.zero?
122
+
123
+ ciphertext.read_string(clen_p.read_ulong_long)
92
124
  end
93
125
 
94
- def remove_zeroes(size, string)
95
- string.slice!(size, string.bytesize - size)
126
+ # Decrypts a ciphertext using XChaCha20-Poly1305
127
+ #
128
+ # @param ciphertext [String] the encrypted data (with tag)
129
+ # @param key [String] 32-byte decryption key
130
+ # @param nonce [String] 24-byte nonce
131
+ # @param add [String] optional associated data
132
+ # @return [String] decrypted plaintext
133
+ def self.decrypt(ciphertext, add, nonce, key)
134
+ raise ArgumentError, 'Invalid key size' unless key.bytesize == KEY_BYTES
135
+ raise ArgumentError, 'Invalid nonce size' unless nonce.bytesize == NONCE_BYTES
136
+
137
+ c_ptr = FFI::MemoryPointer.from_string(ciphertext)
138
+ ad_ptr = FFI::MemoryPointer.from_string(add)
139
+
140
+ m_ptr = FFI::MemoryPointer.new(:uchar, ciphertext.bytesize - TAG_BYTES)
141
+ mlen_p = FFI::MemoryPointer.new(:ulong_long)
142
+
143
+ result = Sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
144
+ m_ptr, mlen_p,
145
+ nil,
146
+ c_ptr, ciphertext.bytesize,
147
+ ad_ptr, add.bytesize,
148
+ FFI::MemoryPointer.from_string(nonce),
149
+ FFI::MemoryPointer.from_string(key)
150
+ )
151
+
152
+ raise 'Decryption failed' unless result.zero?
153
+
154
+ m_ptr.read_string(mlen_p.read_ulong_long)
96
155
  end
97
156
  end
98
157
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ if RUBY_PLATFORM.match?(/mswin|mingw|windows/)
4
+
5
+ # @!visibility private
6
+ module Discordrb::Voice::WinTimer
7
+ extend FFI::Library
8
+
9
+ ffi_lib 'winmm'
10
+
11
+ attach_function :time_begin_period, :timeBeginPeriod, [:uint], :uint
12
+
13
+ attach_function :time_end_period, :timeEndPeriod, [:uint], :uint
14
+ end
15
+
16
+ Discordrb::Voice::WinTimer.time_begin_period(1)
17
+
18
+ at_exit { Discordrb::Voice::WinTimer.time_end_period(1) }
19
+ end