Skip to content

Commit 76c5474

Browse files
authored
Rewrite VoiceEvent.DeserializationStrategy (#925)
* Rewrite VoiceEvent.DeserializationStrategy Similar to #923 but for the voice gateway. * Log and ignore all unexpected opcodes
1 parent b3efeaf commit 76c5474

File tree

4 files changed

+87
-71
lines changed

4 files changed

+87
-71
lines changed

voice/api/voice.api

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -577,17 +577,6 @@ public final class dev/kord/voice/gateway/HeartbeatAck : dev/kord/voice/gateway/
577577
public fun toString ()Ljava/lang/String;
578578
}
579579

580-
public final class dev/kord/voice/gateway/HeartbeatAck$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
581-
public static final field INSTANCE Ldev/kord/voice/gateway/HeartbeatAck$$serializer;
582-
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
583-
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/kord/voice/gateway/HeartbeatAck;
584-
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
585-
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
586-
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/kord/voice/gateway/HeartbeatAck;)V
587-
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
588-
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
589-
}
590-
591580
public final class dev/kord/voice/gateway/HeartbeatAck$Companion {
592581
public final fun serializer ()Lkotlinx/serialization/KSerializer;
593582
}
@@ -656,6 +645,7 @@ public final class dev/kord/voice/gateway/Identify$Companion {
656645

657646
public final class dev/kord/voice/gateway/OpCode : java/lang/Enum {
658647
public static final field ClientDisconnect Ldev/kord/voice/gateway/OpCode;
648+
public static final field Companion Ldev/kord/voice/gateway/OpCode$Companion;
659649
public static final field Heartbeat Ldev/kord/voice/gateway/OpCode;
660650
public static final field HeartbeatAck Ldev/kord/voice/gateway/OpCode;
661651
public static final field Hello Ldev/kord/voice/gateway/OpCode;
@@ -673,6 +663,10 @@ public final class dev/kord/voice/gateway/OpCode : java/lang/Enum {
673663
public static fun values ()[Ldev/kord/voice/gateway/OpCode;
674664
}
675665

666+
public final class dev/kord/voice/gateway/OpCode$Companion {
667+
public final fun serializer ()Lkotlinx/serialization/KSerializer;
668+
}
669+
676670
public final class dev/kord/voice/gateway/Ready : dev/kord/voice/gateway/VoiceEvent {
677671
public static final field Companion Ldev/kord/voice/gateway/Ready$Companion;
678672
public synthetic fun <init> (ILjava/lang/String;ILjava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V

voice/src/main/kotlin/gateway/Command.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import kotlinx.serialization.SerializationStrategy as KSerializationStrategy
1515
public sealed class Command {
1616
public object SerializationStrategy : KSerializationStrategy<Command> {
1717
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Command") {
18-
element("op", OpCode.Serializer.descriptor)
18+
element("op", OpCode.serializer().descriptor)
1919
element("d", JsonObject.serializer().descriptor)
2020
}
2121

@@ -24,23 +24,23 @@ public sealed class Command {
2424

2525
when (value) {
2626
is Identify -> {
27-
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.Identify)
27+
composite.encodeSerializableElement(descriptor, 0, OpCode.serializer(), OpCode.Identify)
2828
composite.encodeSerializableElement(descriptor, 1, Identify.serializer(), value)
2929
}
3030
is Heartbeat -> {
31-
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.Heartbeat)
31+
composite.encodeSerializableElement(descriptor, 0, OpCode.serializer(), OpCode.Heartbeat)
3232
composite.encodeLongElement(descriptor, 1, value.nonce)
3333
}
3434
is SendSpeaking -> {
35-
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.Speaking)
35+
composite.encodeSerializableElement(descriptor, 0, OpCode.serializer(), OpCode.Speaking)
3636
composite.encodeSerializableElement(descriptor, 1, SendSpeaking.serializer(), value)
3737
}
3838
is SelectProtocol -> {
39-
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.SelectProtocol)
39+
composite.encodeSerializableElement(descriptor, 0, OpCode.serializer(), OpCode.SelectProtocol)
4040
composite.encodeSerializableElement(descriptor, 1, SelectProtocol.serializer(), value)
4141
}
4242
is Resume -> {
43-
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.Resume)
43+
composite.encodeSerializableElement(descriptor, 0, OpCode.serializer(), OpCode.Resume)
4444
composite.encodeSerializableElement(descriptor, 1, Resume.serializer(), value)
4545
}
4646
}

voice/src/main/kotlin/gateway/OpCode.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package dev.kord.voice.gateway
22

33
import kotlinx.serialization.KSerializer
4+
import kotlinx.serialization.Serializable
45
import kotlinx.serialization.descriptors.PrimitiveKind
56
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
6-
import kotlinx.serialization.descriptors.SerialDescriptor
77
import kotlinx.serialization.encoding.Decoder
88
import kotlinx.serialization.encoding.Encoder
99

10+
@Serializable(with = OpCode.Serializer::class)
1011
public enum class OpCode(public val code: Int) {
1112
Unknown(Int.MIN_VALUE),
1213
Identify(0),
@@ -22,8 +23,7 @@ public enum class OpCode(public val code: Int) {
2223
ClientDisconnect(13);
2324

2425
internal object Serializer : KSerializer<OpCode> {
25-
override val descriptor: SerialDescriptor
26-
get() = PrimitiveSerialDescriptor("op", PrimitiveKind.INT)
26+
override val descriptor = PrimitiveSerialDescriptor("dev.kord.voice.gateway.OpCode", PrimitiveKind.INT)
2727

2828
private val entriesByCode = entries.associateBy { it.code }
2929
override fun deserialize(decoder: Decoder): OpCode {

voice/src/main/kotlin/gateway/VoiceEvent.kt

Lines changed: 73 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,70 +4,71 @@ import dev.kord.common.entity.Snowflake
44
import dev.kord.voice.EncryptionMode
55
import dev.kord.voice.SpeakingFlags
66
import io.github.oshai.kotlinlogging.KotlinLogging
7-
import kotlinx.serialization.ExperimentalSerializationApi
8-
import kotlinx.serialization.SerialName
9-
import kotlinx.serialization.Serializable
10-
import kotlinx.serialization.builtins.nullable
7+
import kotlinx.serialization.*
8+
import kotlinx.serialization.descriptors.PrimitiveKind
9+
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
1110
import kotlinx.serialization.descriptors.SerialDescriptor
1211
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
1312
import kotlinx.serialization.encoding.CompositeDecoder
1413
import kotlinx.serialization.encoding.Decoder
14+
import kotlinx.serialization.encoding.Encoder
15+
import kotlinx.serialization.encoding.decodeStructure
16+
import kotlinx.serialization.json.JsonDecoder
1517
import kotlinx.serialization.json.JsonElement
1618
import kotlinx.serialization.DeserializationStrategy as KDeserializationStrategy
1719

1820
private val jsonLogger = KotlinLogging.logger { }
1921

2022
public sealed class VoiceEvent {
2123
public object DeserializationStrategy : KDeserializationStrategy<VoiceEvent?> {
22-
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Event") {
23-
element("op", OpCode.Serializer.descriptor)
24-
element("d", JsonElement.serializer().descriptor)
24+
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("dev.kord.voice.gateway.Event") {
25+
element("op", OpCode.serializer().descriptor)
26+
element("d", JsonElement.serializer().descriptor, isOptional = true)
2527
}
2628

27-
@OptIn(ExperimentalSerializationApi::class)
28-
override fun deserialize(decoder: Decoder): VoiceEvent? {
29+
override fun deserialize(decoder: Decoder): VoiceEvent? = decoder.decodeStructure(descriptor) {
2930
var op: OpCode? = null
30-
var data: VoiceEvent? = null
31-
32-
with(decoder.beginStructure(descriptor)) {
33-
loop@ while (true) {
34-
when (val index = decodeElementIndex(descriptor)) {
35-
CompositeDecoder.DECODE_DONE -> break@loop
36-
0 -> op = OpCode.Serializer.deserialize(decoder)
37-
1 -> data = when (op) {
38-
OpCode.Hello -> decodeSerializableElement(
39-
descriptor,
40-
index,
41-
Hello.serializer()
42-
)
43-
OpCode.HeartbeatAck -> {
44-
HeartbeatAck(decodeInlineElement(HeartbeatAck.serializer().descriptor, 0).decodeLong())
45-
}
46-
OpCode.Ready -> decodeSerializableElement(descriptor, index, Ready.serializer())
47-
OpCode.SessionDescription -> decodeSerializableElement(
48-
descriptor,
49-
index,
50-
SessionDescription.serializer()
51-
)
52-
OpCode.Speaking -> decodeSerializableElement(descriptor, index, Speaking.serializer())
53-
OpCode.Resumed -> Resumed
54-
else -> {
55-
val element = decodeNullableSerializableElement(
56-
descriptor,
57-
index,
58-
JsonElement.serializer().nullable
59-
)
60-
61-
jsonLogger.debug { "Unknown event with Opcode $op : $element" }
62-
null
63-
}
64-
}
65-
}
31+
var d: JsonElement? = null
32+
while (true) {
33+
when (val index = decodeElementIndex(descriptor)) {
34+
0 -> op = decodeSerializableElement(descriptor, index, OpCode.serializer(), op)
35+
1 -> d = decodeSerializableElement(descriptor, index, JsonElement.serializer(), d)
36+
CompositeDecoder.DECODE_DONE -> break
37+
else -> throw SerializationException("Unexpected index: $index")
38+
}
39+
}
40+
when (op) {
41+
null ->
42+
throw @OptIn(ExperimentalSerializationApi::class) MissingFieldException("op", descriptor.serialName)
43+
OpCode.Ready -> decodeEvent(decoder, op, Ready.serializer(), d)
44+
OpCode.SessionDescription -> decodeEvent(decoder, op, SessionDescription.serializer(), d)
45+
OpCode.Speaking -> decodeEvent(decoder, op, Speaking.serializer(), d)
46+
OpCode.HeartbeatAck -> decodeEvent(decoder, op, HeartbeatAck.serializer(), d)
47+
OpCode.Hello -> decodeEvent(decoder, op, Hello.serializer(), d)
48+
OpCode.Resumed -> {
49+
// ignore the d field, Resumed is supposed to have null here:
50+
// https://discord.com/developers/docs/topics/voice-connections#resuming-voice-connection-example-resumed-payload
51+
Resumed
52+
}
53+
OpCode.Identify, OpCode.SelectProtocol, OpCode.Heartbeat, OpCode.Resume, OpCode.ClientDisconnect,
54+
OpCode.Unknown,
55+
-> {
56+
jsonLogger.debug { "Unknown voice gateway event with opcode $op : $d" }
57+
null
6658
}
67-
endStructure(descriptor)
68-
return data
6959
}
7060
}
61+
62+
private fun <T> decodeEvent(
63+
decoder: Decoder,
64+
op: OpCode,
65+
deserializer: KDeserializationStrategy<T>,
66+
d: JsonElement?,
67+
): T {
68+
requireNotNull(d) { "Voice gateway event is missing 'd' field for opcode $op" }
69+
// this cast will always succeed, otherwise decoder couldn't have decoded d
70+
return (decoder as JsonDecoder).json.decodeFromJsonElement(deserializer, d)
71+
}
7172
}
7273
}
7374

@@ -87,8 +88,14 @@ public data class Hello(
8788
val heartbeatInterval: Double
8889
) : VoiceEvent()
8990

90-
@Serializable
91-
public data class HeartbeatAck(val nonce: Long) : VoiceEvent()
91+
@Serializable(with = HeartbeatAck.Serializer::class)
92+
public data class HeartbeatAck(val nonce: Long) : VoiceEvent() {
93+
internal object Serializer : KSerializer<HeartbeatAck> {
94+
override val descriptor = PrimitiveSerialDescriptor("dev.kord.voice.gateway.HeartbeatAck", PrimitiveKind.LONG)
95+
override fun serialize(encoder: Encoder, value: HeartbeatAck) = encoder.encodeLong(value.nonce)
96+
override fun deserialize(decoder: Decoder) = HeartbeatAck(nonce = decoder.decodeLong())
97+
}
98+
}
9299

93100
@Serializable
94101
public data class SessionDescription(
@@ -105,8 +112,23 @@ public data class Speaking(
105112
val speaking: SpeakingFlags
106113
) : VoiceEvent()
107114

108-
@Serializable
109-
public object Resumed : VoiceEvent()
115+
public object Resumed : VoiceEvent() {
116+
@Deprecated(
117+
"'Resumed' is no longer serializable, deserialize it with 'VoiceEvent.DeserializationStrategy' instead. " +
118+
"Deprecated without a replacement.",
119+
level = DeprecationLevel.WARNING,
120+
)
121+
public fun serializer(): KSerializer<Resumed> = serializer
122+
123+
private val serializer: KSerializer<Resumed> by lazy(LazyThreadSafetyMode.PUBLICATION) {
124+
@Suppress("INVISIBLE_MEMBER")
125+
kotlinx.serialization.internal.ObjectSerializer(
126+
serialName = "dev.kord.voice.gateway.Resumed",
127+
objectInstance = Resumed,
128+
classAnnotations = arrayOf(),
129+
)
130+
}
131+
}
110132

111133
public sealed class Close : VoiceEvent() {
112134
/**

0 commit comments

Comments
 (0)