Add Cookie API (#1313)

This commit is contained in:
Luccboy 2024-05-30 17:52:30 +02:00 committed by GitHub
parent deacdb6228
commit 07f1f9e7bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 917 additions and 0 deletions

View File

@ -0,0 +1,155 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.event.annotation.AwaitingEvent;
import com.velocitypowered.api.proxy.Player;
import java.util.Arrays;
import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.Nullable;
/**
* This event is fired when a cookie response from a client is received by the proxy.
* This usually happens after either a proxy plugin or a backend server requested a cookie.
* Velocity will wait on this event to finish firing before discarding the
* received cookie (if handled) or forwarding it to the backend server.
*/
@AwaitingEvent
public final class CookieReceiveEvent implements ResultedEvent<CookieReceiveEvent.ForwardResult> {
private final Player player;
private final Key originalKey;
private final byte @Nullable [] originalData;
private ForwardResult result;
/**
* Creates a new instance.
*
* @param player the player who sent the cookie response
* @param key the identifier of the cookie
* @param data the data of the cookie
*/
public CookieReceiveEvent(final Player player, final Key key, final byte @Nullable [] data) {
this.player = Preconditions.checkNotNull(player, "player");
this.originalKey = Preconditions.checkNotNull(key, "key");
this.originalData = data;
this.result = ForwardResult.forward();
}
@Override
public ForwardResult getResult() {
return result;
}
@Override
public void setResult(ForwardResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
public Player getPlayer() {
return player;
}
public Key getOriginalKey() {
return originalKey;
}
public byte @Nullable [] getOriginalData() {
return originalData;
}
@Override
public String toString() {
return "CookieReceiveEvent{"
+ ", originalKey=" + originalKey
+ ", originalData=" + Arrays.toString(originalData)
+ ", result=" + result
+ '}';
}
/**
* A result determining whether or not to forward the cookie response on.
*/
public static final class ForwardResult implements ResultedEvent.Result {
private static final ForwardResult ALLOWED = new ForwardResult(true, null, null);
private static final ForwardResult DENIED = new ForwardResult(false, null, null);
private final boolean status;
private final Key key;
private final byte[] data;
private ForwardResult(final boolean status, final Key key, final byte[] data) {
this.status = status;
this.key = key;
this.data = data;
}
@Override
public boolean isAllowed() {
return status;
}
public Key getKey() {
return key;
}
public byte[] getData() {
return data;
}
@Override
public String toString() {
return status ? "forward to backend server" : "handled by proxy";
}
/**
* Allows the cookie response to be forwarded to the backend server.
*
* @return the forward result
*/
public static ForwardResult forward() {
return ALLOWED;
}
/**
* Prevents the cookie response from being forwarded to the backend server, the cookie response
* is handled by the proxy.
*
* @return the handled result
*/
public static ForwardResult handled() {
return DENIED;
}
/**
* Allows the cookie response to be forwarded to the backend server, but silently replaces the
* identifier of the cookie with another.
*
* @param key the identifier to use instead
* @return a result with a new key
*/
public static ForwardResult key(final Key key) {
Preconditions.checkNotNull(key, "key");
return new ForwardResult(true, key, null);
}
/**
* Allows the cookie response to be forwarded to the backend server, but silently replaces the
* data of the cookie with another.
*
* @param data the data of the cookie to use instead
* @return a result with new data
*/
public static ForwardResult data(final byte[] data) {
return new ForwardResult(true, null, data);
}
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.event.annotation.AwaitingEvent;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.key.Key;
/**
* This event is fired when a cookie is requested from a client either by a proxy plugin or
* by a backend server. Velocity will wait on this event to finish firing before discarding the
* received cookie (if handled) or forwarding it to the backend server.
*/
@AwaitingEvent
public final class CookieRequestEvent implements ResultedEvent<CookieRequestEvent.ForwardResult> {
private final Player player;
private final Key originalKey;
private ForwardResult result;
/**
* Creates a new instance.
*
* @param player the player from whom the cookies is requested
* @param key the identifier of the cookie
*/
public CookieRequestEvent(final Player player, final Key key) {
this.player = Preconditions.checkNotNull(player, "player");
this.originalKey = Preconditions.checkNotNull(key, "key");
this.result = ForwardResult.forward();
}
@Override
public ForwardResult getResult() {
return result;
}
@Override
public void setResult(ForwardResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
public Player getPlayer() {
return player;
}
public Key getOriginalKey() {
return originalKey;
}
@Override
public String toString() {
return "CookieRequestEvent{"
+ ", originalKey=" + originalKey
+ ", result=" + result
+ '}';
}
/**
* A result determining whether or not to forward the cookie request on.
*/
public static final class ForwardResult implements Result {
private static final ForwardResult ALLOWED = new ForwardResult(true, null);
private static final ForwardResult DENIED = new ForwardResult(false, null);
private final boolean status;
private final Key key;
private ForwardResult(final boolean status, final Key key) {
this.status = status;
this.key = key;
}
@Override
public boolean isAllowed() {
return status;
}
public Key getKey() {
return key;
}
@Override
public String toString() {
return status ? "forward to client" : "handled by proxy";
}
/**
* Allows the cookie request to be forwarded to the client.
*
* @return the forward result
*/
public static ForwardResult forward() {
return ALLOWED;
}
/**
* Prevents the cookie request from being forwarded to the client, the cookie request is
* handled by the proxy.
*
* @return the handled result
*/
public static ForwardResult handled() {
return DENIED;
}
/**
* Allows the cookie response to be forwarded to the client, but silently replaces the
* identifier of the cookie with another.
*
* @param key the identifier to use instead
* @return a result with a new key
*/
public static ForwardResult key(final Key key) {
Preconditions.checkNotNull(key, "key");
return new ForwardResult(true, key);
}
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.event.annotation.AwaitingEvent;
import com.velocitypowered.api.proxy.Player;
import java.util.Arrays;
import net.kyori.adventure.key.Key;
/**
* This event is fired when a cookie should be stored on a player's client. This process can be
* initiated either by a proxy plugin or by a backend server. Velocity will wait on this event
* to finish firing before discarding the cookie (if handled) or forwarding it to the client so
* that it can store the cookie.
*/
@AwaitingEvent
public final class CookieStoreEvent implements ResultedEvent<CookieStoreEvent.ForwardResult> {
private final Player player;
private final Key originalKey;
private final byte[] originalData;
private ForwardResult result;
/**
* Creates a new instance.
*
* @param player the player who should store the cookie
* @param key the identifier of the cookie
* @param data the data of the cookie
*/
public CookieStoreEvent(final Player player, final Key key, final byte[] data) {
this.player = Preconditions.checkNotNull(player, "player");
this.originalKey = Preconditions.checkNotNull(key, "key");
this.originalData = Preconditions.checkNotNull(data, "data");
this.result = ForwardResult.forward();
}
@Override
public ForwardResult getResult() {
return result;
}
@Override
public void setResult(ForwardResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
public Player getPlayer() {
return player;
}
public Key getOriginalKey() {
return originalKey;
}
public byte[] getOriginalData() {
return originalData;
}
@Override
public String toString() {
return "CookieStoreEvent{"
+ ", originalKey=" + originalKey
+ ", originalData=" + Arrays.toString(originalData)
+ ", result=" + result
+ '}';
}
/**
* A result determining whether or not to forward the cookie on.
*/
public static final class ForwardResult implements Result {
private static final ForwardResult ALLOWED = new ForwardResult(true, null, null);
private static final ForwardResult DENIED = new ForwardResult(false, null, null);
private final boolean status;
private final Key key;
private final byte[] data;
private ForwardResult(final boolean status, final Key key, final byte[] data) {
this.status = status;
this.key = key;
this.data = data;
}
@Override
public boolean isAllowed() {
return status;
}
public Key getKey() {
return key;
}
public byte[] getData() {
return data;
}
@Override
public String toString() {
return status ? "forward to client" : "handled by proxy";
}
/**
* Allows the cookie to be forwarded to the client so that it can store it.
*
* @return the forward result
*/
public static ForwardResult forward() {
return ALLOWED;
}
/**
* Prevents the cookie from being forwarded to the client, the cookie is handled by the proxy.
*
* @return the handled result
*/
public static ForwardResult handled() {
return DENIED;
}
/**
* Allows the cookie to be forwarded to the client so that it can store it, but silently
* replaces the identifier of the cookie with another.
*
* @param key the identifier to use instead
* @return a result with a new key
*/
public static ForwardResult key(final Key key) {
Preconditions.checkNotNull(key, "key");
return new ForwardResult(true, key, null);
}
/**
* Allows the cookie to be forwarded to the client so that it can store it, but silently
* replaces the data of the cookie with another.
*
* @param data the data of the cookie to use instead
* @return a result with new data
*/
public static ForwardResult data(final byte[] data) {
Preconditions.checkNotNull(data, "data");
return new ForwardResult(true, null, data);
}
}
}

View File

@ -8,6 +8,7 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.player.CookieReceiveEvent;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
@ -436,4 +437,28 @@ public interface Player extends
* @since 3.3.0
*/
void transferToHost(@NotNull InetSocketAddress address);
/**
* Stores a cookie with arbitrary data on the player's client.
*
* @param key the identifier of the cookie
* @param data the data of the cookie
* @throws IllegalArgumentException if the player is from a version lower than 1.20.5
* @since 3.3.0
* @sinceMinecraft 1.20.5
*/
void storeCookie(Key key, byte[] data);
/**
* Requests a previously stored cookie from the player's client.
* Calling this method causes the client to send the cookie to the proxy.
* To retrieve the actual data of the requested cookie, you have to use the
* {@link CookieReceiveEvent}.
*
* @param key the identifier of the cookie
* @throws IllegalArgumentException if the player is from a version lower than 1.20.5
* @since 3.3.0
* @sinceMinecraft 1.20.5
*/
void requestCookie(Key key);
}

View File

@ -22,6 +22,8 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket;
@ -45,6 +47,7 @@ import com.velocitypowered.proxy.protocol.packet.RespawnPacket;
import com.velocitypowered.proxy.protocol.packet.ServerDataPacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
@ -339,4 +342,16 @@ public interface MinecraftSessionHandler {
default boolean handle(KnownPacksPacket packet) {
return false;
}
default boolean handle(ClientboundStoreCookiePacket packet) {
return false;
}
default boolean handle(ClientboundCookieRequestPacket packet) {
return false;
}
default boolean handle(ServerboundCookieResponsePacket packet) {
return false;
}
}

View File

@ -26,6 +26,8 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.connection.PreTransferEvent;
import com.velocitypowered.api.event.player.CookieRequestEvent;
import com.velocitypowered.api.event.player.CookieStoreEvent;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
@ -48,6 +50,8 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket;
@ -70,6 +74,7 @@ import io.netty.channel.Channel;
import io.netty.handler.timeout.ReadTimeoutException;
import java.net.InetSocketAddress;
import java.util.regex.Pattern;
import net.kyori.adventure.key.Key;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -390,6 +395,39 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return true;
}
@Override
public boolean handle(ClientboundStoreCookiePacket packet) {
server.getEventManager()
.fire(new CookieStoreEvent(serverConn.getPlayer(), packet.getKey(), packet.getPayload()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final Key resultedKey = event.getResult().getKey() == null
? event.getOriginalKey() : event.getResult().getKey();
final byte[] resultedData = event.getResult().getData() == null
? event.getOriginalData() : event.getResult().getData();
playerConnection.write(new ClientboundStoreCookiePacket(resultedKey, resultedData));
}
}, playerConnection.eventLoop());
return true;
}
@Override
public boolean handle(ClientboundCookieRequestPacket packet) {
server.getEventManager().fire(new CookieRequestEvent(serverConn.getPlayer(), packet.getKey()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final Key resultedKey = event.getResult().getKey() == null
? event.getOriginalKey() : event.getResult().getKey();
playerConnection.write(new ClientboundCookieRequestPacket(resultedKey));
}
}, playerConnection.eventLoop());
return true;
}
@Override
public void handleGeneric(MinecraftPacket packet) {
if (packet instanceof PluginMessagePacket) {

View File

@ -18,6 +18,8 @@
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.connection.PreTransferEvent;
import com.velocitypowered.api.event.player.CookieRequestEvent;
import com.velocitypowered.api.event.player.CookieStoreEvent;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
import com.velocitypowered.api.network.ProtocolVersion;
@ -34,6 +36,8 @@ import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
@ -48,6 +52,7 @@ import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.key.Key;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -251,6 +256,40 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
return true;
}
@Override
public boolean handle(ClientboundStoreCookiePacket packet) {
server.getEventManager()
.fire(new CookieStoreEvent(serverConn.getPlayer(), packet.getKey(), packet.getPayload()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final Key resultedKey = event.getResult().getKey() == null
? event.getOriginalKey() : event.getResult().getKey();
final byte[] resultedData = event.getResult().getData() == null
? event.getOriginalData() : event.getResult().getData();
serverConn.getPlayer().getConnection()
.write(new ClientboundStoreCookiePacket(resultedKey, resultedData));
}
}, serverConn.ensureConnected().eventLoop());
return true;
}
@Override
public boolean handle(ClientboundCookieRequestPacket packet) {
server.getEventManager().fire(new CookieRequestEvent(serverConn.getPlayer(), packet.getKey()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final Key resultedKey = event.getResult().getKey() == null
? event.getOriginalKey() : event.getResult().getKey();
serverConn.getPlayer().getConnection().write(new ClientboundCookieRequestPacket(resultedKey));
}
}, serverConn.ensureConnected().eventLoop());
return true;
}
@Override
public void disconnected() {
resultFuture.completeExceptionally(

View File

@ -17,6 +17,7 @@
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.player.CookieRequestEvent;
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
@ -31,6 +32,8 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket;
@ -43,6 +46,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -174,6 +178,26 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
return true;
}
@Override
public boolean handle(ClientboundStoreCookiePacket packet) {
throw new IllegalStateException("Can only store cookie in CONFIGURATION or PLAY protocol");
}
@Override
public boolean handle(ClientboundCookieRequestPacket packet) {
server.getEventManager().fire(new CookieRequestEvent(serverConn.getPlayer(), packet.getKey()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final Key resultedKey = event.getResult().getKey() == null
? event.getOriginalKey() : event.getResult().getKey();
serverConn.getPlayer().getConnection().write(new ClientboundCookieRequestPacket(resultedKey));
}
}, serverConn.ensureConnected().eventLoop());
return true;
}
@Override
public void exception(Throwable throwable) {
resultFuture.completeExceptionally(throwable);

View File

@ -24,6 +24,7 @@ import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
import com.velocitypowered.api.event.player.CookieReceiveEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
import com.velocitypowered.api.network.ProtocolVersion;
@ -41,6 +42,7 @@ import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
import io.netty.buffer.ByteBuf;
import java.util.Objects;
@ -188,6 +190,24 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
return true;
}
@Override
public boolean handle(ServerboundCookieResponsePacket packet) {
server.getEventManager()
.fire(new CookieReceiveEvent(connectedPlayer, packet.getKey(), packet.getPayload()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
// The received cookie must have been requested by a proxy plugin in login phase,
// because if a backend server requests a cookie in login phase, the client is already
// in config phase. Therefore, the only way, we receive a CookieResponsePacket from a
// client in login phase is when a proxy plugin requested a cookie in login phase.
throw new IllegalStateException(
"A cookie was requested by a proxy plugin in login phase but the response wasn't handled");
}
}, mcConnection.eventLoop());
return true;
}
private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) {
mcConnection.setAssociation(player);

View File

@ -17,6 +17,7 @@
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.event.player.CookieReceiveEvent;
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
@ -32,6 +33,7 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket;
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
@ -39,6 +41,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.logging.log4j.LogManager;
@ -144,6 +147,28 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
return false;
}
@Override
public boolean handle(ServerboundCookieResponsePacket packet) {
server.getEventManager()
.fire(new CookieReceiveEvent(player, packet.getKey(), packet.getPayload()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final VelocityServerConnection serverConnection = player.getConnectionInFlight();
if (serverConnection != null) {
final Key resultedKey = event.getResult().getKey() == null
? event.getOriginalKey() : event.getResult().getKey();
final byte[] resultedData = event.getResult().getData() == null
? event.getOriginalData() : event.getResult().getData();
serverConnection.ensureConnected()
.write(new ServerboundCookieResponsePacket(resultedKey, resultedData));
}
}
}, player.getConnection().eventLoop());
return true;
}
@Override
public void handleGeneric(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();

View File

@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.suggestion.Suggestion;
import com.velocitypowered.api.command.VelocityBrigadierMessage;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.CookieReceiveEvent;
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent;
@ -48,6 +49,7 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
import com.velocitypowered.proxy.protocol.packet.RespawnPacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequestPacket;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket.Offer;
@ -83,6 +85,7 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.logging.log4j.LogManager;
@ -418,6 +421,28 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return true;
}
@Override
public boolean handle(ServerboundCookieResponsePacket packet) {
server.getEventManager()
.fire(new CookieReceiveEvent(player, packet.getKey(), packet.getPayload()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null) {
final Key resultedKey = event.getResult().getKey() == null
? event.getOriginalKey() : event.getResult().getKey();
final byte[] resultedData = event.getResult().getData() == null
? event.getOriginalData() : event.getResult().getData();
serverConnection.ensureConnected()
.write(new ServerboundCookieResponsePacket(resultedKey, resultedData));
}
}
}, player.getConnection().eventLoop());
return true;
}
@Override
public void handleGeneric(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();

View File

@ -27,6 +27,8 @@ import com.google.gson.JsonObject;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.DisconnectEvent.LoginStatus;
import com.velocitypowered.api.event.connection.PreTransferEvent;
import com.velocitypowered.api.event.player.CookieRequestEvent;
import com.velocitypowered.api.event.player.CookieStoreEvent;
import com.velocitypowered.api.event.player.KickedFromServerEvent;
import com.velocitypowered.api.event.player.KickedFromServerEvent.DisconnectPlayer;
import com.velocitypowered.api.event.player.KickedFromServerEvent.Notify;
@ -65,6 +67,8 @@ import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket;
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
@ -105,6 +109,7 @@ import java.util.concurrent.ThreadLocalRandom;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.permission.PermissionChecker;
import net.kyori.adventure.platform.facet.FacetPointers;
import net.kyori.adventure.platform.facet.FacetPointers.Type;
@ -1008,6 +1013,50 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
});
}
@Override
public void storeCookie(final Key key, final byte[] data) {
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(data);
Preconditions.checkArgument(
this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_5),
"Player version must be at least 1.20.5 to be able to store cookies");
if (connection.getState() != StateRegistry.PLAY
&& connection.getState() != StateRegistry.CONFIG) {
throw new IllegalStateException("Can only store cookie in CONFIGURATION or PLAY protocol");
}
server.getEventManager().fire(new CookieStoreEvent(this, key, data))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final Key resultedKey = event.getResult().getKey() == null
? event.getOriginalKey() : event.getResult().getKey();
final byte[] resultedData = event.getResult().getData() == null
? event.getOriginalData() : event.getResult().getData();
connection.write(new ClientboundStoreCookiePacket(resultedKey, resultedData));
}
}, connection.eventLoop());
}
@Override
public void requestCookie(final Key key) {
Preconditions.checkNotNull(key);
Preconditions.checkArgument(
this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_5),
"Player version must be at least 1.20.5 to be able to retrieve cookies");
server.getEventManager().fire(new CookieRequestEvent(this, key))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final Key resultedKey = event.getResult().getKey() == null
? event.getOriginalKey() : event.getResult().getKey();
connection.write(new ClientboundCookieRequestPacket(resultedKey));
}
}, connection.eventLoop());
}
@Override
public void addCustomChatCompletions(@NotNull Collection<String> completions) {
Preconditions.checkNotNull(completions, "completions");

View File

@ -52,6 +52,8 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket;
@ -73,6 +75,7 @@ import com.velocitypowered.proxy.protocol.packet.RespawnPacket;
import com.velocitypowered.proxy.protocol.packet.ServerDataPacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
@ -146,6 +149,9 @@ public enum StateRegistry {
serverbound.register(
ClientSettingsPacket.class, ClientSettingsPacket::new,
map(0x00, MINECRAFT_1_20_2, false));
serverbound.register(
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
map(0x01, MINECRAFT_1_20_5, false));
serverbound.register(
PluginMessagePacket.class, PluginMessagePacket::new,
map(0x01, MINECRAFT_1_20_2, false),
@ -171,6 +177,9 @@ public enum StateRegistry {
KnownPacksPacket::new,
map(0x07, MINECRAFT_1_20_5, false));
clientbound.register(
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
map(0x00, MINECRAFT_1_20_5, false));
clientbound.register(
PluginMessagePacket.class, PluginMessagePacket::new,
map(0x00, MINECRAFT_1_20_2, false),
@ -202,6 +211,9 @@ public enum StateRegistry {
map(0x06, MINECRAFT_1_20_2, false),
map(0x07, MINECRAFT_1_20_3, false),
map(0x09, MINECRAFT_1_20_5, false));
clientbound.register(
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
map(0x0A, MINECRAFT_1_20_5, false));
clientbound.register(TransferPacket.class, TransferPacket::new,
map(0x0B, MINECRAFT_1_20_5, false));
clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new,
@ -276,6 +288,9 @@ public enum StateRegistry {
map(0x08, MINECRAFT_1_19_4, false),
map(0x09, MINECRAFT_1_20_2, false),
map(0x0A, MINECRAFT_1_20_5, false));
serverbound.register(
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
map(0x11, MINECRAFT_1_20_5, false));
serverbound.register(
PluginMessagePacket.class,
PluginMessagePacket::new,
@ -374,6 +389,9 @@ public enum StateRegistry {
map(0x0E, MINECRAFT_1_19_3, false),
map(0x10, MINECRAFT_1_19_4, false),
map(0x11, MINECRAFT_1_20_2, false));
clientbound.register(
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
map(0x16, MINECRAFT_1_20_5, false));
clientbound.register(
PluginMessagePacket.class,
PluginMessagePacket::new,
@ -595,6 +613,9 @@ public enum StateRegistry {
map(0x3A, MINECRAFT_1_19_4, false),
map(0x3C, MINECRAFT_1_20_2, false),
map(0x3E, MINECRAFT_1_20_5, false));
clientbound.register(
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
map(0x6B, MINECRAFT_1_20_5, false));
clientbound.register(
SystemChatPacket.class,
SystemChatPacket::new,
@ -654,6 +675,9 @@ public enum StateRegistry {
serverbound.register(
LoginAcknowledgedPacket.class, LoginAcknowledgedPacket::new,
map(0x03, MINECRAFT_1_20_2, false));
serverbound.register(
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
map(0x04, MINECRAFT_1_20_5, false));
clientbound.register(
DisconnectPacket.class, () -> new DisconnectPacket(this),
@ -671,6 +695,9 @@ public enum StateRegistry {
LoginPluginMessagePacket.class,
LoginPluginMessagePacket::new,
map(0x04, MINECRAFT_1_13, false));
clientbound.register(
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
map(0x05, MINECRAFT_1_20_5, false));
}
};

View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
public class ClientboundCookieRequestPacket implements MinecraftPacket {
private Key key;
public Key getKey() {
return key;
}
public ClientboundCookieRequestPacket() {
}
public ClientboundCookieRequestPacket(final Key key) {
this.key = key;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.key = ProtocolUtils.readKey(buf);
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeKey(buf, key);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
public class ClientboundStoreCookiePacket implements MinecraftPacket {
private Key key;
private byte[] payload;
public Key getKey() {
return key;
}
public byte[] getPayload() {
return payload;
}
public ClientboundStoreCookiePacket() {
}
public ClientboundStoreCookiePacket(final Key key, final byte[] payload) {
this.key = key;
this.payload = payload;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.key = ProtocolUtils.readKey(buf);
this.payload = ProtocolUtils.readByteArray(buf, 5120);
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeKey(buf, key);
ProtocolUtils.writeByteArray(buf, payload);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ServerboundCookieResponsePacket implements MinecraftPacket {
private Key key;
private byte @Nullable [] payload;
public Key getKey() {
return key;
}
public byte @Nullable [] getPayload() {
return payload;
}
public ServerboundCookieResponsePacket() {
}
public ServerboundCookieResponsePacket(final Key key, final byte @Nullable [] payload) {
this.key = key;
this.payload = payload;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.key = ProtocolUtils.readKey(buf);
if (buf.readBoolean()) {
this.payload = ProtocolUtils.readByteArray(buf, 5120);
}
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeKey(buf, key);
final boolean hasPayload = payload != null && payload.length > 0;
buf.writeBoolean(hasPayload);
if (hasPayload) {
ProtocolUtils.writeByteArray(buf, payload);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}