Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DAVE protocol support #32

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ Koe includes modified/stripped-down parts based on following open-source project

- [tweetnacl-java](https://github.com/InstantWebP2P/tweetnacl-java) (Poly1305, SecretBox)
- [nanojson](https://github.com/mmastrac/nanojson) (modified for bytebuf support, changed the API a bit and etc.)
- [BouncyCastle MLS](https://github.com/bcgit/bc-java/tree/1.79) (see README.md in mls module for more info)
- [libdave](https://github.com/discord/libdave) (Koe's DAVE implementation is heavily based on reference C++ implementation)
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ def getGitVersion() {
return [versionStr.toString().trim(), true]
}

ext {
nettyVersion = '4.1.112.Final'
slf4jVersion = '1.8.0-beta4'
tinkVersion = '1.15.0'
bouncyCastleVersion = '1.79'
jetbrainsAnnotationsVersion = '13.0'
}

subprojects {
apply plugin: 'maven-publish'
apply plugin: 'java-library'
Expand Down
12 changes: 6 additions & 6 deletions core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
dependencies {
api group: 'io.netty', name: 'netty-transport', version: '4.1.112.Final'
implementation group: 'io.netty', name: 'netty-codec-http', version: '4.1.112.Final'
implementation group: 'io.netty', name: 'netty-transport-native-epoll', version: '4.1.112.Final', classifier: 'linux-x86_64'
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.8.0-beta4'
implementation group: 'com.google.crypto.tink', name: 'tink', version: '1.14.1'
compileOnly group: 'org.jetbrains', name: 'annotations', version: '13.0'
api group: 'io.netty', name: 'netty-transport', version: "$nettyVersion"
implementation group: 'io.netty', name: 'netty-codec-http', version: "$nettyVersion"
implementation group: 'io.netty', name: 'netty-transport-native-epoll', version: "$nettyVersion", classifier: 'linux-x86_64'
implementation group: 'org.slf4j', name: 'slf4j-api', version: "$slf4jVersion"
implementation group: 'com.google.crypto.tink', name: 'tink', version: "$tinkVersion"
compileOnly group: 'org.jetbrains', name: 'annotations', version: "$jetbrainsAnnotationsVersion"
}
2 changes: 1 addition & 1 deletion core/src/main/java/moe/kyokobot/koe/KoeOptionsBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class KoeOptionsBuilder {
: NioDatagramChannel.class;

this.byteBufAllocator = new PooledByteBufAllocator();
this.gatewayVersion = GatewayVersion.V4;
this.gatewayVersion = GatewayVersion.V8;
this.framePollerFactory = new NettyFramePollerFactory();
this.highPacketPriority = true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package moe.kyokobot.koe.gateway;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
Expand All @@ -16,8 +17,8 @@
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.EventExecutor;
import moe.kyokobot.koe.VoiceServerInfo;
import moe.kyokobot.koe.internal.NettyBootstrapFactory;
import moe.kyokobot.koe.internal.MediaConnectionImpl;
import moe.kyokobot.koe.internal.NettyBootstrapFactory;
import moe.kyokobot.koe.internal.json.JsonObject;
import moe.kyokobot.koe.internal.json.JsonParser;
import moe.kyokobot.koe.internal.util.NettyFutureWrapper;
Expand Down Expand Up @@ -116,6 +117,10 @@ public boolean isOpen() {

protected abstract void handlePayload(JsonObject object);

protected void handleBinaryPayload(ByteBuf buffer) {
// no-op
}

protected void onClose(int code, @Nullable String reason, boolean remote) {
if (!closed) {
closed = true;
Expand Down Expand Up @@ -148,14 +153,29 @@ public void sendInternalPayload(int op, Object d) {
sendRaw(new JsonObject().add("op", op).add("d", d));
}

public void sendBinaryInternalPayload(char op, ByteBuf buffer) {
var frame = channel.alloc().buffer(1 + buffer.readableBytes());
frame.writeByte(op);
frame.writeBytes(buffer);
sendRaw(frame);
buffer.release();
}

protected void sendRaw(JsonObject object) {
if (channel != null && channel.isOpen()) {
var data = object.toString();
logger.trace("<- {}", data);
logger.trace("<-T {}", data);
channel.writeAndFlush(new TextWebSocketFrame(data));
}
}

protected void sendRaw(ByteBuf buffer) {
if (channel != null && channel.isOpen()) {
logger.trace("<-B {}", buffer);
channel.writeAndFlush(new BinaryWebSocketFrame(buffer));
}
}

private class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
private final WebSocketClientHandshaker handshaker;

Expand Down Expand Up @@ -210,9 +230,14 @@ protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Except
if (msg instanceof TextWebSocketFrame) {
var frame = (TextWebSocketFrame) msg;
var object = JsonParser.object().from(frame.content());
logger.trace("-> {}", object);
logger.trace("->T {}", object);
frame.release();
handlePayload(object);
} else if (msg instanceof BinaryWebSocketFrame) {
var frame = (BinaryWebSocketFrame) msg;
logger.trace("->B {}", frame.content());
handleBinaryPayload(frame.content());
frame.release();
} else if (msg instanceof CloseWebSocketFrame) {
var frame = (CloseWebSocketFrame) msg;
if (logger.isDebugEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ protected void handlePayload(JsonObject object) {
logger.debug("Resumed successfully");
break;
}
case Op.CLIENT_CONNECT: {
case Op.VIDEO: {
var data = object.getObject("d");
var user = data.getString("user_id");
var audioSsrc = data.getInt("audio_ssrc", 0);
Expand Down Expand Up @@ -195,7 +195,7 @@ private void selectProtocol(String protocol) {
.add("data", udpInfo)
.combine(udpInfo));

sendInternalPayload(Op.CLIENT_CONNECT, new JsonObject()
sendInternalPayload(Op.VIDEO, new JsonObject()
.add("audio_ssrc", ssrc)
.add("video_ssrc", 0)
.add("rtx_ssrc", 0));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ protected void handlePayload(JsonObject object) {
logger.debug("Resumed successfully");
break;
}
case Op.CLIENT_CONNECT: {
case Op.VIDEO: {
var data = object.getObject("d");
var user = data.getString("user_id");
var audioSsrc = data.getInt("audio_ssrc", 0);
Expand All @@ -129,7 +129,7 @@ protected void handlePayload(JsonObject object) {
connection.getDispatcher().userDisconnected(user);
break;
}
case Op.VIDEO_SINK_WANTS: {
case Op.MEDIA_SINK_WANTS: {
// Sent only if `video` flag was true while identifying. At time of writing this comment Discord forces
// it to false on bots (so.. user bot time? /s) due to voice server bug that broke clients or something.
// After receiving this opcode client can send op 12 with ssrcs for video (audio + 1)
Expand Down Expand Up @@ -211,7 +211,7 @@ private void selectProtocol(String protocol) {

this.updateSpeaking(0);

sendInternalPayload(Op.CLIENT_CONNECT, new JsonObject()
sendInternalPayload(Op.VIDEO, new JsonObject()
.add("audio_ssrc", ssrc)
.add("video_ssrc", 0)
.add("rtx_ssrc", 0));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package moe.kyokobot.koe.gateway;

import io.netty.buffer.ByteBuf;
import moe.kyokobot.koe.VoiceServerInfo;
import moe.kyokobot.koe.codec.Codec;
import moe.kyokobot.koe.codec.DefaultCodecs;
Expand Down Expand Up @@ -46,7 +47,9 @@ protected void identify() {
.addAsString("user_id", connection.getClient().getClientId())
.add("session_id", voiceServerInfo.getSessionId())
.add("token", voiceServerInfo.getToken())
.add("video", true));
.add("video", true)
.add("max_dave_protocol_version", 0)
.add("streams", new JsonArray()));
}

@Override
Expand Down Expand Up @@ -120,7 +123,7 @@ protected void handlePayload(JsonObject object) {
logger.debug("Resumed successfully");
break;
}
case Op.CLIENT_CONNECT: {
case Op.VIDEO: {
var data = object.getObject("d");
var user = data.getString("user_id");
var audioSsrc = data.getInt("audio_ssrc", 0);
Expand All @@ -135,7 +138,7 @@ protected void handlePayload(JsonObject object) {
connection.getDispatcher().userDisconnected(user);
break;
}
case Op.VIDEO_SINK_WANTS: {
case Op.MEDIA_SINK_WANTS: {
// Sent only if `video` flag was true while identifying. At time of writing this comment Discord forces
// it to false on bots (so.. user bot time? /s) due to voice server bug that broke clients or something.
// After receiving this opcode client can send op 12 with ssrcs for video (audio + 1)
Expand All @@ -151,6 +154,13 @@ protected void handlePayload(JsonObject object) {
}
}

@Override
protected void handleBinaryPayload(ByteBuf buffer) {
sequence = buffer.readUnsignedShort();

var op = buffer.readUnsignedByte();
}

@Override
protected void onClose(int code, @Nullable String reason, boolean remote) {
super.onClose(code, reason, remote);
Expand Down Expand Up @@ -219,7 +229,7 @@ private void selectProtocol(String protocol) {

this.updateSpeaking(0);

sendInternalPayload(Op.CLIENT_CONNECT, new JsonObject()
sendInternalPayload(Op.VIDEO, new JsonObject()
.add("audio_ssrc", ssrc)
.add("video_ssrc", 0)
.add("rtx_ssrc", 0));
Expand Down
21 changes: 18 additions & 3 deletions core/src/main/java/moe/kyokobot/koe/gateway/Op.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,24 @@ private Op() {
public static final int HELLO = 8;
public static final int RESUMED = 9;
// public static final int DUNNO = 10;
// public static final int DUNNO = 11;
public static final int CLIENT_CONNECT = 12; // thx b1nzy
public static final int CLIENT_CONNECT = 11;
public static final int VIDEO = 12;
public static final int CLIENT_DISCONNECT = 13;
public static final int CODECS = 14;
public static final int VIDEO_SINK_WANTS = 15;
public static final int MEDIA_SINK_WANTS = 15;
public static final int VOICE_BACKEND_VERSION = 16;
public static final int CHANNEL_OPTIONS_UPDATE = 17;
public static final int CLIENT_FLAGS = 18;
public static final int SPEED_TEST = 19;
public static final int PLATFORM = 20;
public static final int SECURE_FRAMES_PREPARE_PROTOCOL_TRANSITION = 21;
public static final int SECURE_FRAMES_EXECUTE_TRANSITION = 22;
public static final int SECURE_FRAMES_READY_FOR_TRANSITION = 23;
public static final int SECURE_FRAMES_PREPARE_EPOCH = 24;
public static final int MLS_EXTERNAL_SENDER_PACKAGE = 25;
public static final int MLS_KEY_PACKAGE = 26;
public static final int MLS_PROPOSALS = 27;
public static final int MLS_COMMIT_WELCOME = 28;
public static final int MLS_PREPARE_COMMIT_TRANSITION = 29;
public static final int MLS_WELCOME = 30;
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void handleSessionDescription(JsonObject object) {

if (encryptionMode == null) {
throw new IllegalStateException("Encryption mode selected by Discord is not supported by Koe or the " +
"protocol changed! Open an issue at https://github.com/KyokoBot/koe");
"protocol changed! Update to latest version or open an issue at https://github.com/KyokoBot/koe");
}

var keyArray = object.getArray("secret_key");
Expand Down
6 changes: 6 additions & 0 deletions dave/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dependencies {
implementation project(':mls')
implementation group: 'org.slf4j', name: 'slf4j-api', version: "$slf4jVersion"
compileOnly group: 'org.jetbrains', name: 'annotations', version: "$jetbrainsAnnotationsVersion"
}

11 changes: 11 additions & 0 deletions dave/src/main/java/moe/kyokobot/koe/dave/DAVEException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package moe.kyokobot.koe.dave;

public class DAVEException extends Exception {
public DAVEException(String message) {
super(message);
}

public DAVEException(String message, Throwable cause) {
super(message, cause);
}
}
7 changes: 7 additions & 0 deletions dave/src/main/java/moe/kyokobot/koe/dave/KeyRatchet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package moe.kyokobot.koe.dave;

public interface KeyRatchet {
byte[] getKey(int keyGeneration);

void deleteKey(int keyGeneration);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package moe.kyokobot.koe.dave;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;

public interface PersistentKeyManager {
AsymmetricCipherKeyPair getKeyPair(String signingKeyId, int protocolVersion);
}
20 changes: 20 additions & 0 deletions dave/src/main/java/moe/kyokobot/koe/dave/mls/MLSKeyRatchet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package moe.kyokobot.koe.dave.mls;

import moe.kyokobot.koe.dave.KeyRatchet;
import moe.kyokobot.koe.mls.crypto.MlsCipherSuite;

public class MLSKeyRatchet implements KeyRatchet {
public MLSKeyRatchet(MlsCipherSuite suite, byte[] baseSecret) {

}

@Override
public byte[] getKey(int keyGeneration) {
return new byte[0];
}

@Override
public void deleteKey(int keyGeneration) {

}
}
Loading