Compare commits

...
64 Commits
Author SHA1 Message Date
yoyosource 664826fec3 Merge remote-tracking branch 'upstream/dev/4.0.0' into update
SteamWarCI Build failed
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
2026-09-24 17:05:06 +02:00
Rocco fb0f21f425 Update online mode in reconfigure join packet (#1889) 2026-09-23 14:09:07 +01:00
MiniDigger | Martin 5bb1992014 [ci skip] update namespace badge 2026-09-22 16:09:28 +02:00
MiniDigger | Martin f975230f14 [ci skip] migrate CI to namespace 🚀 (#1888) 2026-09-20 10:23:18 +02:00
Wouter Gritter a6f9de9581 Bound IntervalledCounter size under packet floods
The counter stored one entry per addTime call, so a client sending tiny
(even empty) packets could grow it without limit while staying under the
bytes-per-second limit, eventually OOMing the proxy. Merge data points
within 1ms of the newest one so the entry count is bounded by the window
size (~7000 for the default 7s window) rather than by packet rate.
2026-09-16 13:41:57 +02:00
Wouter Gritter 5000227d11 Snapshot 4.2.1-SNAPSHOT 2026-09-16 13:41:50 +02:00
Wouter Gritter c10b492554 Release 4.2.0 2026-09-14 18:36:05 +02:00
Aaron dff94da2b3 Minecraft 26.3 (#1867)
* 26.3-snapshot-7

* 26.3-snapshot-8

* 26.3-snapshot-9

* 26.3-snapshot-10

* 26.3-pre-1

* 26.3-pre-2

* feat: add post effects packet

* chore: write game modes as var int

* 26.3-pre-3

* 26.3-rc-1

* 26.3

* chore: add key array cap

* fix: add a zero
2026-09-14 18:34:59 +02:00
Braydon 1b48f40534 Fix config reload not removing deleted servers (#1882)
* Fix config reload not removing deleted servers

When reloading velocity.toml, unregister servers that were removed from
the config and evacuate any connected players to fallback servers.

* Requested PR fixes

* Make Expected ConnectedPlayer exceptions more detailed
2026-09-14 18:34:50 +02:00
Wouter Gritter bc55300123 Bump com.puppycrawl.tools:checkstyle to 14.1.0 and fix warnings that were false-negatives before [ci skip]
Checkstyle fixed a bunch of false-negatives; linting errors in the source that should've been caught according to our settings. Rather than updating `checkstyle.xml` to work around these, this commit also properly fixes these checkstyle errors.
2026-09-13 14:01:39 +02:00
yoyosource a3b6177059 Trigger rebuild
SteamWarCI Build successful
2026-08-27 08:31:23 +02:00
yoyosource 3ef5b2ab99 Trigger rebuild
SteamWarCI Build successful
2026-08-27 08:25:34 +02:00
yoyosource 7a8bf8a5e0 Trigger rebuild
SteamWarCI Build failed
2026-08-27 08:22:06 +02:00
yoyosource 05de745780 Trigger rebuild
SteamWarCI Build successful
2026-08-27 08:19:47 +02:00
yoyosource 75ce1a9bf7 Fix StateRegistry maybe
SteamWarCI Build successful
2026-08-27 08:12:17 +02:00
yoyosource 7a39ebb058 Fix StateRegistry and update to dev/4.0.0
SteamWarCI Build successful
2026-08-24 17:14:49 +02:00
yoyosource 57e9cd7f24 Merge remote-tracking branch 'upstream/dev/4.0.0'
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java
2026-08-24 17:03:57 +02:00
YoyoNow b9463125d1 Merge pull request 'Update26' (#3) from update26 into master
SteamWarCI Build successful
Reviewed-on: #3
2026-08-13 09:23:04 +02:00
Chaoscaot 67e16a308e Merge remote-tracking branch 'upstream/dev/4.0.0'
SteamWarCI Build successful
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java
2026-07-13 21:48:01 +02:00
yoyosource 4b8ceb5264 Fix UpdateTeamsPacket
SteamWarCI Build successful
2026-05-21 09:53:29 +02:00
yoyosource 2d1cade7bd Fix UpdateTeamsPacket
SteamWarCI Build successful
2026-05-21 09:19:40 +02:00
yoyosource 4067c44cd1 Fix UpdateTeamsPacket
SteamWarCI Build successful
2026-05-21 09:06:32 +02:00
yoyosource 45bb5de601 Fix Packet ID in 1.21.9 for UpdateTeamsPacket
SteamWarCI Build successful
2026-05-21 08:52:49 +02:00
yoyosource ebb9eb046d Fix UpdateTeamsPacket for 1.21.5+
SteamWarCI Build successful
2026-05-21 08:35:30 +02:00
Chaoscaot afc84b88ac Fix
SteamWarCI Build successful
2026-05-21 08:30:09 +02:00
Chaoscaot e6caa87865 Fix
SteamWarCI Build failed
2026-05-21 08:26:58 +02:00
Chaoscaot cdeabe91d0 Merge remote-tracking branch 'upstream/dev/3.0.0'
SteamWarCI Build failed
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
2026-05-21 08:23:49 +02:00
Chaoscaot babb22bf88 Remove duplicate mapping for MINECRAFT_1_21_2 in StateRegistry
SteamWarCI Build successful
2025-11-09 01:51:08 +01:00
Chaoscaot 468127996c Merge remote-tracking branch 'upstream/dev/3.0.0'
SteamWarCI Build failed
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
2025-11-09 01:46:27 +01:00
Chaoscaot 873fca763d Merge remote-tracking branch 'upstream/dev/3.0.0'
SteamWarCI Build successful
2025-07-28 18:34:52 +02:00
Chaoscaot 67d63faeca Reapply "Disable io_uring transport by default"
SteamWarCI Build successful
This reverts commit 11834de220.
2025-07-10 10:42:11 +02:00
Chaoscaot 871b053561 Merge remote-tracking branch 'upstream/dev/3.0.0' 2025-07-10 10:41:50 +02:00
Chaoscaot c2edc26d8e Merge pull request 'Update Velocity' (#2) from update into master
SteamWarCI Build successful
Reviewed-on: #2
2025-06-26 23:11:02 +02:00
Chaoscaot 76417b13d4 Merge branch 'updatev2' into update
SteamWarCI Build successful
2025-06-26 22:53:32 +02:00
Chaoscaot 47f36e3ff9 Merge remote-tracking branch 'upstream/dev/3.0.0' into updatev2
SteamWarCI Build successful
2025-06-26 22:52:31 +02:00
Chaoscaot 75bb48d00e Merge remote-tracking branch 'upstream/dev/3.0.0' into updatev2
SteamWarCI Build successful
2025-06-03 23:12:58 +02:00
Chaoscaot 11834de220 Revert "Disable io_uring transport by default"
SteamWarCI Build successful
This reverts commit ae312339a3.
2025-05-02 20:49:11 +02:00
Chaoscaot 91a61643bd Revert "Disable io_uring transport by default"
SteamWarCI Build successful
This reverts commit ae312339a3.
2025-04-27 20:24:41 +02:00
Chaoscaot b6e05cb0b9 Refactor TCP Fast Open checks and update message identifiers.
SteamWarCI Build successful
Removed transport type conditions for TCP Fast Open to streamline configuration usage. Added imports for new message identifiers in `ClientPlaySessionHandler`. Cleaned up Netty library definitions in `libs.versions.toml`.
2025-04-27 20:09:05 +02:00
Chaoscaot 1507b91463 Merge remote-tracking branch 'upstream/dev/3.0.0' into update
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java
2025-04-27 19:53:15 +02:00
Lixfel b06af3718c Merge remote-tracking branch 'github/dev/3.0.0'
SteamWarCI Build successful
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java
2025-04-01 07:06:14 +02:00
Lixfel a20a896582 Skip javadoc generation
SteamWarCI Build successful
2025-01-22 09:37:30 +01:00
Lixfel e1a3421212 Adapt to new server
SteamWarCI Build failed
2025-01-22 09:33:59 +01:00
Lixfel 19e51a2b12 Merge remote-tracking branch 'upstream/dev/3.0.0' 2024-12-06 11:14:27 +01:00
Lixfel b89a5c5ce9 Fix CI 2024-12-02 12:45:04 +01:00
Lixfel 65d3277319 Merge remote-tracking branch 'upstream/dev/3.0.0' 2024-11-30 09:25:25 +01:00
Lixfel a22bfa10f9 Merge pull request 'Update 1.21.2 client support' (#5) from upstream into master
Reviewed-on: https://steamwar.de/devlabs/SteamWar/Velocity/pulls/5
2024-11-11 08:15:32 +01:00
Lixfel d9d1319a3a Merge remote-tracking branch 'upstream/dev/3.0.0' into upstream
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
2024-11-10 18:32:48 +01:00
Lixfel 15ecbf4345 Merge pull request 'Update Velocity (might fix Command problems) and fix PluginMessages...' (#4) from fix-pluginmessages into master
Reviewed-on: https://steamwar.de/devlabs/SteamWar/Velocity/pulls/4
Reviewed-by: YoyoNow <jwsteam@nidido.de>
2024-08-20 08:14:11 +02:00
Lixfel 5e3bbcd427 Fix command signature issues. 2024-08-20 08:03:18 +02:00
Lixfel a6c79db07b Remove filter checks to receive PluginMessages unfiltered. 2024-08-18 15:32:08 +02:00
Lixfel 6e33bc6c17 Merge remote-tracking branch 'refs/remotes/upstream/dev/3.0.0' 2024-08-18 15:28:10 +02:00
Lixfel 01208bb359 Indicate NoChatReports support in ServerPing 2024-06-24 18:36:32 +02:00
Lixfel fa88aaae52 Always unsign chat. 2024-06-21 12:48:58 +02:00
Lixfel 2da400a267 Merge pull request 'Implement Velocity PRs #998 #1246 and #1309 (io_uring, tcp_fastopen and PluginMessage race condition fix)' (#3) from io_uring into master
Reviewed-on: https://steamwar.de/devlabs/SteamWar/Velocity/pulls/3
Reviewed-by: YoyoNow <jwsteam@nidido.de>
2024-06-19 12:06:10 +02:00
Lixfel 8103135dfb Fix type 2024-06-19 10:21:36 +02:00
Lixfel cfabff7288 Implement Velocity PRs #998 #1246 and #1309 (io_uring, tcp_fastopen and PluginMessage race condition fix) 2024-06-19 10:11:32 +02:00
Lixfel 2f5a27a708 Fix CI 2024-06-19 09:47:18 +02:00
Lixfel fdfe8bcc4b Fix CI 2024-06-19 09:40:58 +02:00
Chaoscaot a19fd8db74 Add UpdateTeamsPacket 2024-06-16 21:27:13 +02:00
Chaoscaot e63d71423d Add UpdateTeamsPacket 2024-06-16 21:24:06 +02:00
Lixfel a7afe35fab Rebuild 2024-06-16 13:25:07 +02:00
Lixfel 56d6339313 Fix JVM 2024-06-16 13:18:29 +02:00
Lixfel 2475572573 Add steamwarci.yml 2024-06-16 12:52:43 +02:00
42 changed files with 706 additions and 170 deletions
+15 -5
View File
@@ -6,18 +6,28 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-24.04
runs-on: namespace-profile-linux-default
steps:
- name: Checkout Repository
uses: actions/checkout@v6
uses: namespacelabs/nscloud-checkout-action@v9
with:
persist-credentials: false
- name: Set up cache
uses: namespacelabs/nscloud-cache-action@v1
with:
cache: gradle
- name: Setup gradle build cache
run: |
nsc cache gradle setup --init-gradle /tmp/init.gradle
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
with:
cache-disabled: true # namespace handles this
- name: Set up JDK 21
uses: actions/setup-java@v5
uses: actions/setup-java@v6
with:
java-version: 21
distribution: 'zulu'
cache: "" # namespace handles this
- name: Build with Gradle
run: ./gradlew build
run: ./gradlew --init-script=/tmp/init.gradle build
+2
View File
@@ -40,3 +40,5 @@ page.
Translations are handled using [Crowdin](https://papermc-io.crowdin.com/velocity).
If you want to translate a language not available on Crowdin,
you might want to ask in the [Discord](https://discord.gg/papermc) about it.
[![CI powered by namespace badge](https://papermc.io/assets/misc/namespace-oss-badge.svg?project=velocity)](https://namespace.so/github-actions/?utm_source=oss&utm_campaign=papermc)
@@ -73,6 +73,7 @@ public final class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLogin
/**
* Returns the UUID of the connecting player.
*
* <p>This value is {@code null} on 1.19.2 and lower,
* up to 1.20.1 it is optional and from 1.20.2 it will always be available.</p>
*
@@ -40,9 +40,9 @@ public final class PlayerClientBrandEvent {
@Override
public String toString() {
return "PlayerClientBrandEvent{"
+ "player=" + player
+ ", brand='" + brand + '\''
+ '}';
+ "player=" + player
+ ", brand='" + brand + '\''
+ '}';
}
}
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
/**
* This event is executed when a player entered the configuration state and can be configured by Velocity.
*
* <p>Velocity will wait for this event before continuing/ending the configuration state.</p>
*
* @param player The player who can be configured.
@@ -15,6 +15,7 @@ import org.jetbrains.annotations.NotNull;
/**
* This event is executed when a player is about to enter the configuration state.
* It is <b>not</b> called for the initial configuration of a player after login.
*
* <p>Velocity will wait for this event before asking the client to enter configuration state.
* However due to backend server being unable to keep the connection alive during state changes,
* Velocity will only wait for a maximum of 5 seconds.</p>
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
/**
* This event is executed when a player has entered the configuration state.
*
* <p>From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed,
* the {@linkplain Player#getProtocolState()} method is guaranteed
* to return {@link ProtocolState#CONFIGURATION}.</p>
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
/**
* This event is executed when a player is about to finish the configuration state.
*
* <p>Velocity will wait for this event before asking the client to finish the configuration state.
* However due to backend server being unable to keep the connection alive during state changes,
* Velocity will only wait for a maximum of 5 seconds. If you need to hold a player in configuration
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
/**
* This event is executed when a player has finished the configuration state.
*
* <p>From this moment on, the {@link Player#getProtocolState()} method
* will return {@link ProtocolState#PLAY}.</p>
*
@@ -16,6 +16,7 @@ package com.velocitypowered.api.network;
public enum ProtocolState {
/**
* Initial connection State.
*
* <p>This status can be caused by a {@link HandshakeIntent#STATUS},
* {@link HandshakeIntent#LOGIN} or {@link HandshakeIntent#TRANSFER} intent.</p>
* If the intent is LOGIN or TRANSFER, the next state will be {@link #LOGIN},
@@ -24,6 +25,7 @@ public enum ProtocolState {
HANDSHAKE,
/**
* Ping State of a connection.
*
* <p>Connections with the {@link HandshakeIntent#STATUS} intent will pass through this state
* and be disconnected after it requests the ping from the server
* and the server responds with the respective ping.</p>
@@ -31,11 +33,13 @@ public enum ProtocolState {
STATUS,
/**
* Authentication State of a connection.
*
* <p>At this moment the player is authenticating with the authentication servers.</p>
*/
LOGIN,
/**
* Configuration State of a connection.
*
* <p>At this point the player allows the server to send information
* such as resource packs and plugin messages, at the same time the player
* will send his client brand and the respective plugin messages
@@ -46,6 +50,7 @@ public enum ProtocolState {
CONFIGURATION,
/**
* Game State of a connection.
*
* <p>In this state is where the whole game runs, the server is able to change
* the player's state to {@link #CONFIGURATION} as needed in versions 1.20.2 and higher.</p>
*/
@@ -96,7 +96,8 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
MINECRAFT_1_21_9(773, "1.21.9", "1.21.10"),
MINECRAFT_1_21_11(774, "1.21.11"),
MINECRAFT_26_1(775, "26.1", "26.1.1", "26.1.2"),
MINECRAFT_26_2(776, "26.2");
MINECRAFT_26_2(776, "26.2"),
MINECRAFT_26_3(777, "26.3");
private static final int SNAPSHOT_BIT = 30;
@@ -15,7 +15,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* Represents three different states of a setting.
*
* <p>Possible values:</p>
* <p></p>
* <ul>
* <li>{@link #TRUE} - a positive setting</li>
* <li>{@link #FALSE} - a negative (negated) setting</li>
@@ -244,7 +244,7 @@ public interface Player extends
* Gets the {@link ResourcePackInfo} of the currently applied
* resource-pack or null if none.
*
* <p> Note that since 1.20.3 it is no longer recommended to use
* <p>Note that since 1.20.3 it is no longer recommended to use
* this method as it will only return the last applied
* resource pack. To get all applied resource packs, use
* {@link #getAppliedResourcePacks()} instead. </p>
@@ -260,7 +260,7 @@ public interface Player extends
* the user is currently downloading or is currently
* prompted to install or null if none.
*
* <p> Note that since 1.20.3 it is no longer recommended to use
* <p>Note that since 1.20.3 it is no longer recommended to use
* this method as it will only return the last pending
* resource pack. To get all pending resource packs, use
* {@link #getPendingResourcePacks()} instead. </p>
@@ -315,6 +315,7 @@ public interface Player extends
/**
* {@inheritDoc}
*
* <p><strong>Note that this method does not send a plugin message to the server the player
* is connected to.</strong> You should only use this method if you are trying to communicate
* with a mod that is installed on the player's client.</p>
@@ -400,7 +401,7 @@ public interface Player extends
* and will not perform any actions.</b>
* @see #playSound(Sound, Sound.Emitter)
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
* Unsupported Adventure Operations</a>
*/
@Override
default void playSound(@NotNull Sound sound) {
@@ -413,7 +414,7 @@ public interface Player extends
* and will not perform any actions.</b>
* @see #playSound(Sound, Sound.Emitter)
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
* Unsupported Adventure Operations</a>
*/
@Override
default void playSound(@NotNull Sound sound, double x, double y, double z) {
@@ -456,7 +457,7 @@ public interface Player extends
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
* Unsupported Adventure Operations</a>
*/
@Override
default void openBook(@NotNull Book book) {
@@ -469,7 +470,7 @@ public interface Player extends
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
* Unsupported Adventure Operations</a>
*/
@Override
default void showDialog(@NotNull DialogLike dialog) {
@@ -482,7 +483,7 @@ public interface Player extends
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
* Unsupported Adventure Operations</a>
*/
@Override
default void closeDialog() {
@@ -56,6 +56,7 @@ public interface KeySigned {
* Validates the signature, expiry temporal and key against the
* signer public key. Note: This will **not** check for
* expiry. You can check for expiry with {@link KeySigned#hasExpired()}.
*
* <p>DOES NOT WORK YET FOR MESSAGES AND COMMANDS!</p>
* Addendum: Does not work for 1.19.1 until the user has authenticated.
*
@@ -237,8 +237,11 @@ public interface TabListEntry extends KeyIdentifiable {
/**
* Sets the {@link IdentifiedKey} of the {@link TabListEntry}.
*
* <p>This only works for players currently <b>not</b> connected to this proxy.</p>
*
* <p>For any player currently connected to this proxy this will be filled automatically.</p>
*
* <p>Will ignore mismatching key revisions data.</p>
*
* @param chatSession session to set
@@ -33,6 +33,7 @@ public final class ServerPing {
private final @Nullable Component description;
private final @Nullable Favicon favicon;
private final @Nullable ModInfo modinfo;
private final boolean preventsChatReports = true;
public ServerPing(Version version, @Nullable Players players,
net.kyori.adventure.text.Component description, @Nullable Favicon favicon) {
+1 -1
View File
@@ -12,7 +12,7 @@ subprojects {
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
languageVersion.set(JavaLanguageVersion.of(21))
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
group=com.velocitypowered
version=4.1.2-SNAPSHOT
version=4.2.1-SNAPSHOT
+1 -1
View File
@@ -20,7 +20,7 @@ brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
bstats = "org.bstats:bstats-base:3.2.1"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.4"
checker-qual = "org.checkerframework:checker-qual:4.2.3"
checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3"
checkstyle = "com.puppycrawl.tools:checkstyle:14.1.0"
completablefutures = "com.spotify:completable-futures:0.3.6"
configurate3-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate3" }
configurate3-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate3" }
@@ -54,8 +54,8 @@ public class MoreByteBufUtils {
BufferPreference preferred = nativeStuff.preferredBufferType();
return switch (preferred) {
case DIRECT_PREFERRED, HEAP_PREFERRED ->
// The native prefers this type, but doesn't strictly require we provide it.
true;
// The native prefers this type, but doesn't strictly require we provide it.
true;
case DIRECT_REQUIRED -> buf.hasMemoryAddress();
case HEAP_REQUIRED -> buf.hasArray();
};
@@ -488,23 +488,46 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
return false;
}
// Re-register servers. If a server is being replaced, make sure to note what players need to
// move back to a fallback server.
// Re-register servers. If a server is being replaced or removed, make sure to note what
// players need to move back to a fallback server.
Collection<ConnectedPlayer> evacuate = new ArrayList<>();
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
if (newConfiguration.getServers().keySet().stream()
.anyMatch(name -> name.equalsIgnoreCase(entry.getKey()))) {
continue;
}
RegisteredServer rs = servers.getServer(entry.getKey()).orElse(null);
if (rs == null) {
continue;
}
for (Player player : rs.getPlayersConnected()) {
if (!(player instanceof ConnectedPlayer)) {
throw new IllegalStateException("Expected ConnectedPlayer for player " + player
+ " on server " + rs.getServerInfo().getName() + ", got "
+ player.getClass().getName());
}
evacuate.add((ConnectedPlayer) player);
}
servers.unregister(rs.getServerInfo());
}
for (Map.Entry<String, String> entry : newConfiguration.getServers().entrySet()) {
ServerInfo newInfo = new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue()));
Optional<RegisteredServer> rs = servers.getServer(entry.getKey());
if (rs.isEmpty()) {
RegisteredServer rs = servers.getServer(entry.getKey()).orElse(null);
if (rs == null) {
servers.register(newInfo);
} else if (!rs.get().getServerInfo().equals(newInfo)) {
for (Player player : rs.get().getPlayersConnected()) {
} else if (!rs.getServerInfo().equals(newInfo)) {
for (Player player : rs.getPlayersConnected()) {
if (!(player instanceof ConnectedPlayer)) {
throw new IllegalStateException("ConnectedPlayer not found for player " + player
+ " in server " + rs.get().getServerInfo().getName());
throw new IllegalStateException("Expected ConnectedPlayer for player " + player
+ " on server " + rs.getServerInfo().getName() + ", got "
+ player.getClass().getName());
}
evacuate.add((ConnectedPlayer) player);
}
servers.unregister(rs.get().getServerInfo());
servers.unregister(rs.getServerInfo());
servers.register(newInfo);
}
}
@@ -653,7 +676,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
}
/**
* Calls {@link #shutdown(boolean, Component)} with the default reason "Proxy shutting down."
* Calls {@link #shutdown(boolean, Component)} with the default reason "Proxy shutting down".
*
* @param explicitExit whether the user explicitly shut down the proxy
*/
@@ -59,6 +59,6 @@ public record PingPassthroughMode(boolean version, boolean players,
public boolean enabled() {
return this.version || this.players || this.description || this.favicon
|| this.modinfo;
|| this.modinfo;
}
}
@@ -23,6 +23,7 @@ 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.ClientboundPostEffectsPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket;
import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket;
@@ -404,4 +405,8 @@ public interface MinecraftSessionHandler {
default boolean handle(ClientboundStopSoundPacket packet) {
return false;
}
default boolean handle(ClientboundPostEffectsPacket packet) {
return false;
}
}
@@ -34,6 +34,8 @@ import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.command.CommandGraphInjector;
@@ -55,6 +57,7 @@ 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.JoinGamePacket;
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket;
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
@@ -164,6 +167,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return false; // forwards on
}
@Override
public boolean handle(JoinGamePacket packet) {
// We receive this if the connection is reconfigured
packet.setOnlineMode(serverConn.getPlayer().isOnlineMode());
return false; // forward
}
@Override
public boolean handle(ClientSettingsPacket packet) {
serverConn.ensureConnected().write(packet);
@@ -312,13 +322,9 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return true;
}
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
return false;
}
byte[] copy = ByteBufUtil.getBytes(packet.content());
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, copy);
String channel = packet.getChannel();
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), channel.indexOf(':') == -1 ? new LegacyChannelIdentifier(channel) : MinecraftChannelIdentifier.from(channel), copy);
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed() && !playerConnection.isClosed()) {
PluginMessagePacket copied = new PluginMessagePacket(
@@ -31,6 +31,8 @@ import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.event.player.configuration.PlayerEnteredConfigurationEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection;
@@ -37,7 +37,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Modern (Minecraft 1.20.3+) ResourcePackHandler
* Modern (Minecraft 1.20.3+) ResourcePackHandler.
*/
public final class ModernResourcePackHandler extends ResourcePackHandler {
private final ListMultimap<UUID, ResourcePackInfo> outstandingResourcePacks =
@@ -118,6 +118,7 @@ public abstract sealed class ResourcePackHandler
/**
* Processes a client response to a sent resource-pack.
*
* <p>Cases in which no action will be taken:</p>
* <ul>
*
@@ -545,6 +545,36 @@ public class VelocityEventManager implements EventManager {
}
}
private <E> void fire(final @Nullable CompletableFuture<E> future, final E event,
final int offset, final boolean currentlyAsync, final HandlerRegistration[] registrations) {
for (int i = offset; i < registrations.length; i++) {
final HandlerRegistration registration = registrations[i];
try {
final EventTask eventTask = registration.handler.executeAsync(event);
if (eventTask == null) {
continue;
}
final ContinuationTask<E> continuationTask = new ContinuationTask<>(eventTask,
registrations, future, event, i, currentlyAsync);
if (currentlyAsync || !eventTask.requiresAsync()) {
if (continuationTask.execute()) {
continue;
}
} else {
registration.plugin.getExecutorService().execute(continuationTask);
}
// fire will continue in another thread once the async task is
// executed and the continuation is resumed
return;
} catch (final Throwable t) {
logHandlerException(registration, t);
}
}
if (future != null) {
future.complete(event);
}
}
private static final int TASK_STATE_DEFAULT = 0;
private static final int TASK_STATE_EXECUTING = 1;
private static final int TASK_STATE_CONTINUE_IMMEDIATELY = 2;
@@ -669,36 +699,6 @@ public class VelocityEventManager implements EventManager {
}
}
private <E> void fire(final @Nullable CompletableFuture<E> future, final E event,
final int offset, final boolean currentlyAsync, final HandlerRegistration[] registrations) {
for (int i = offset; i < registrations.length; i++) {
final HandlerRegistration registration = registrations[i];
try {
final EventTask eventTask = registration.handler.executeAsync(event);
if (eventTask == null) {
continue;
}
final ContinuationTask<E> continuationTask = new ContinuationTask<>(eventTask,
registrations, future, event, i, currentlyAsync);
if (currentlyAsync || !eventTask.requiresAsync()) {
if (continuationTask.execute()) {
continue;
}
} else {
registration.plugin.getExecutorService().execute(continuationTask);
}
// fire will continue in another thread once the async task is
// executed and the continuation is resumed
return;
} catch (final Throwable t) {
logHandlerException(registration, t);
}
}
if (future != null) {
future.complete(event);
}
}
private static void logHandlerException(
final HandlerRegistration registration, final Throwable t) {
final PluginDescription pluginDescription = registration.plugin.getDescription();
@@ -143,6 +143,7 @@ public enum ProtocolUtils {
.build();
public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
private static final int DEFAULT_MAX_KEY_ARRAY_SIZE = 1024;
private static final int MAXIMUM_VARINT_SIZE = 5;
private static final BinaryTagType<? extends BinaryTag>[] BINARY_TAG_TYPES = new BinaryTagType[] {
BinaryTagTypes.END, BinaryTagTypes.BYTE, BinaryTagTypes.SHORT, BinaryTagTypes.INT,
@@ -349,15 +350,20 @@ public enum ProtocolUtils {
writeString(buf, key.asMinimalString());
}
public static Key[] readKeyArray(ByteBuf buf) {
return readKeyArray(buf, DEFAULT_MAX_KEY_ARRAY_SIZE);
}
/**
* Reads a standard Mojang Text namespaced:key array from the buffer.
*
* @param buf the buffer to read from
* @return the decoded key array
*/
public static Key[] readKeyArray(ByteBuf buf) {
public static Key[] readKeyArray(ByteBuf buf, int cap) {
int length = readVarInt(buf);
checkFrame(length >= 0, "Got a negative-length array (%s)", length);
checkFrame(length <= cap, "Bad key array size (got %s, maximum is %s)", length, cap);
checkFrame(buf.isReadable(length),
"Trying to read an array that is too long (wanted %s, only have %s)", length,
buf.readableBytes());
@@ -644,7 +650,7 @@ public enum ProtocolUtils {
private static final int FORGE_MAX_ARRAY_LENGTH = Integer.MAX_VALUE & 0x1FFF9A;
/**
* Reads an byte array for legacy version 1.7 from the specified {@code buf}
* Reads an byte array for legacy version 1.7 from the specified {@code buf}.
*
* @param buf the buffer to read from
* @return the read byte array
@@ -685,7 +691,7 @@ public enum ProtocolUtils {
}
/**
* Writes an byte array for legacy version 1.7 to the specified {@code buf}
* Writes an byte array for legacy version 1.7 to the specified {@code buf}.
*
* @param b array
* @param buf buf
@@ -709,7 +715,7 @@ public enum ProtocolUtils {
}
/**
* Writes an {@link ByteBuf} for legacy version 1.7 to the specified {@code buf}
* Writes an {@link ByteBuf} for legacy version 1.7 to the specified {@code buf}.
*
* @param b array
* @param buf buf
@@ -18,37 +18,7 @@
package com.velocitypowered.proxy.protocol;
import static com.google.common.collect.Iterables.getLast;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12_1;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_14;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_15;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16_4;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_17;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_4;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_6;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_9;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9_4;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_1;
import static com.velocitypowered.api.network.ProtocolVersion.MINIMUM_VERSION;
import static com.velocitypowered.api.network.ProtocolVersion.SUPPORTED_VERSIONS;
import static com.velocitypowered.api.network.ProtocolVersion.*;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction.CLIENTBOUND;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction.SERVERBOUND;
@@ -234,31 +204,44 @@ 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(
ClientboundPostEffectsPacket.class, ClientboundPostEffectsPacket::new,
map(0x0A, MINECRAFT_26_3, false));
clientbound.register(
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
map(0x0A, MINECRAFT_1_20_5, false));
map(0x0A, MINECRAFT_1_20_5, false),
map(0x0B, MINECRAFT_26_3, false));
clientbound.register(TransferPacket.class, TransferPacket::new,
map(0x0B, MINECRAFT_1_20_5, false));
map(0x0B, MINECRAFT_1_20_5, false),
map(0x0C, MINECRAFT_26_3, false));
clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new,
map(0x07, MINECRAFT_1_20_2, false),
map(0x08, MINECRAFT_1_20_3, false),
map(0x0C, MINECRAFT_1_20_5, false));
map(0x0C, MINECRAFT_1_20_5, false),
map(0x0D, MINECRAFT_26_3, false));
clientbound.register(TagsUpdatePacket.class, TagsUpdatePacket::new,
map(0x08, MINECRAFT_1_20_2, false),
map(0x09, MINECRAFT_1_20_3, false),
map(0x0D, MINECRAFT_1_20_5, false));
map(0x0D, MINECRAFT_1_20_5, false),
map(0x0E, MINECRAFT_26_3, false));
clientbound.register(KnownPacksPacket.class, KnownPacksPacket::new,
map(0x0E, MINECRAFT_1_20_5, false));
map(0x0E, MINECRAFT_1_20_5, false),
map(0x0F, MINECRAFT_26_3, false));
clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
map(0x0F, MINECRAFT_1_21, false));
map(0x0F, MINECRAFT_1_21, false),
map(0x10, MINECRAFT_26_3, false));
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
map(0x10, MINECRAFT_1_21, false));
map(0x10, MINECRAFT_1_21, false),
map(0x11, MINECRAFT_26_3, false));
clientbound.register(DialogClearPacket.class, () -> DialogClearPacket.INSTANCE,
map(0x11, MINECRAFT_1_21_6, false));
map(0x11, MINECRAFT_1_21_6, false),
map(0x12, MINECRAFT_26_3, false));
clientbound.register(DialogShowPacket.class, () -> new DialogShowPacket(this),
map(0x12, MINECRAFT_1_21_6, false));
map(0x12, MINECRAFT_1_21_6, false),
map(0x13, MINECRAFT_26_3, false));
clientbound.register(CodeOfConductPacket.class, CodeOfConductPacket::new,
map(0x13, MINECRAFT_1_21_9, false));
map(0x13, MINECRAFT_1_21_9, false),
map(0x14, MINECRAFT_26_3, false));
}
},
PLAY {
@@ -410,7 +393,8 @@ public enum StateRegistry {
map(0x2D, MINECRAFT_1_21_2, false),
map(0x2F, MINECRAFT_1_21_4, false),
map(0x30, MINECRAFT_1_21_6, false),
map(0x31, MINECRAFT_26_1, false));
map(0x31, MINECRAFT_26_1, false),
map(0x32, MINECRAFT_26_3, false));
serverbound.register(
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
map(0x0B, MINECRAFT_1_20_2, false),
@@ -469,6 +453,9 @@ public enum StateRegistry {
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
map(0x16, MINECRAFT_1_20_5, false),
map(0x15, MINECRAFT_1_21_5, false));
clientbound.register(
ClientboundPostEffectsPacket.class, ClientboundPostEffectsPacket::new,
map(0x53, MINECRAFT_26_3, false));
clientbound.register(
ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new,
map(0x5D, MINECRAFT_1_19_3, true),
@@ -479,7 +466,8 @@ public enum StateRegistry {
map(0x6E, MINECRAFT_1_21_2, true),
map(0x6D, MINECRAFT_1_21_5, true),
map(0x72, MINECRAFT_1_21_9, true),
map(0x74, MINECRAFT_26_1, true));
map(0x74, MINECRAFT_26_1, true),
map(0x76, MINECRAFT_26_3, true));
clientbound.register(
ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new,
map(0x5F, MINECRAFT_1_19_3, true),
@@ -490,7 +478,8 @@ public enum StateRegistry {
map(0x71, MINECRAFT_1_21_2, true),
map(0x70, MINECRAFT_1_21_5, true),
map(0x75, MINECRAFT_1_21_9, true),
map(0x77, MINECRAFT_26_1, true));
map(0x77, MINECRAFT_26_1, true),
map(0x79, MINECRAFT_26_3, true));
clientbound.register(
PluginMessagePacket.class,
PluginMessagePacket::new,
@@ -548,7 +537,8 @@ public enum StateRegistry {
map(0x27, MINECRAFT_1_21_2, false),
map(0x26, MINECRAFT_1_21_5, false),
map(0x2B, MINECRAFT_1_21_9, false),
map(0x2C, MINECRAFT_26_1, false));
map(0x2C, MINECRAFT_26_1, false),
map(0x2D, MINECRAFT_26_3, false));
clientbound.register(
JoinGamePacket.class,
JoinGamePacket::new,
@@ -569,7 +559,8 @@ public enum StateRegistry {
map(0x2C, MINECRAFT_1_21_2, false),
map(0x2B, MINECRAFT_1_21_5, false),
map(0x30, MINECRAFT_1_21_9, false),
map(0x31, MINECRAFT_26_1, false));
map(0x31, MINECRAFT_26_1, false),
map(0x32, MINECRAFT_26_3, false));
clientbound.register(
RespawnPacket.class,
RespawnPacket::new,
@@ -593,7 +584,8 @@ public enum StateRegistry {
map(0x4C, MINECRAFT_1_21_2, true),
map(0x4B, MINECRAFT_1_21_5, true),
map(0x50, MINECRAFT_1_21_9, true),
map(0x52, MINECRAFT_26_1, true));
map(0x52, MINECRAFT_26_1, true),
map(0x54, MINECRAFT_26_3, true));
clientbound.register(
RemoveResourcePackPacket.class,
RemoveResourcePackPacket::new,
@@ -602,7 +594,8 @@ public enum StateRegistry {
map(0x4A, MINECRAFT_1_21_2, false),
map(0x49, MINECRAFT_1_21_5, false),
map(0x4E, MINECRAFT_1_21_9, false),
map(0x50, MINECRAFT_26_1, false));
map(0x50, MINECRAFT_26_1, false),
map(0x51, MINECRAFT_26_3, false));
clientbound.register(
ResourcePackRequestPacket.class,
ResourcePackRequestPacket::new,
@@ -626,7 +619,8 @@ public enum StateRegistry {
map(0x4B, MINECRAFT_1_21_2, false),
map(0x4A, MINECRAFT_1_21_5, false),
map(0x4F, MINECRAFT_1_21_9, false),
map(0x51, MINECRAFT_26_1, false));
map(0x51, MINECRAFT_26_1, false),
map(0x52, MINECRAFT_26_3, false));
clientbound.register(
HeaderAndFooterPacket.class,
HeaderAndFooterPacket::new,
@@ -651,7 +645,8 @@ public enum StateRegistry {
map(0x74, MINECRAFT_1_21_2, true),
map(0x73, MINECRAFT_1_21_5, true),
map(0x78, MINECRAFT_1_21_9, true),
map(0x7A, MINECRAFT_26_1, true));
map(0x7A, MINECRAFT_26_1, true),
map(0x7D, MINECRAFT_26_3, true));
clientbound.register(
LegacyTitlePacket.class,
LegacyTitlePacket::new,
@@ -675,7 +670,8 @@ public enum StateRegistry {
map(0x6A, MINECRAFT_1_21_2, true),
map(0x69, MINECRAFT_1_21_5, true),
map(0x6E, MINECRAFT_1_21_9, true),
map(0x70, MINECRAFT_26_1, true));
map(0x70, MINECRAFT_26_1, true),
map(0x72, MINECRAFT_26_3, true));
clientbound.register(
TitleTextPacket.class,
TitleTextPacket::new,
@@ -690,7 +686,8 @@ public enum StateRegistry {
map(0x6C, MINECRAFT_1_21_2, true),
map(0x6B, MINECRAFT_1_21_5, true),
map(0x70, MINECRAFT_1_21_9, true),
map(0x72, MINECRAFT_26_1, true));
map(0x72, MINECRAFT_26_1, true),
map(0x74, MINECRAFT_26_3, true));
clientbound.register(
TitleActionbarPacket.class,
TitleActionbarPacket::new,
@@ -705,7 +702,8 @@ public enum StateRegistry {
map(0x51, MINECRAFT_1_21_2, true),
map(0x50, MINECRAFT_1_21_5, true),
map(0x55, MINECRAFT_1_21_9, true),
map(0x57, MINECRAFT_26_1, true));
map(0x57, MINECRAFT_26_1, true),
map(0x59, MINECRAFT_26_3, true));
clientbound.register(
TitleTimesPacket.class,
TitleTimesPacket::new,
@@ -720,7 +718,8 @@ public enum StateRegistry {
map(0x6D, MINECRAFT_1_21_2, true),
map(0x6C, MINECRAFT_1_21_5, true),
map(0x71, MINECRAFT_1_21_9, true),
map(0x73, MINECRAFT_26_1, true));
map(0x73, MINECRAFT_26_1, true),
map(0x75, MINECRAFT_26_3, true));
clientbound.register(
TitleClearPacket.class,
TitleClearPacket::new,
@@ -752,7 +751,8 @@ public enum StateRegistry {
map(0x3F, MINECRAFT_1_21_2, false),
map(0x3E, MINECRAFT_1_21_5, false),
map(0x43, MINECRAFT_1_21_9, false),
map(0x45, MINECRAFT_26_1, false));
map(0x45, MINECRAFT_26_1, false),
map(0x46, MINECRAFT_26_3, false));
clientbound.register(
UpsertPlayerInfoPacket.class,
UpsertPlayerInfoPacket::new,
@@ -763,14 +763,16 @@ public enum StateRegistry {
map(0x40, MINECRAFT_1_21_2, false),
map(0x3F, MINECRAFT_1_21_5, false),
map(0x44, MINECRAFT_1_21_9, false),
map(0x46, MINECRAFT_26_1, false));
map(0x46, MINECRAFT_26_1, false),
map(0x47, MINECRAFT_26_3, false));
clientbound.register(
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
map(0x6B, MINECRAFT_1_20_5, false),
map(0x72, MINECRAFT_1_21_2, false),
map(0x71, MINECRAFT_1_21_5, false),
map(0x76, MINECRAFT_1_21_9, false),
map(0x78, MINECRAFT_26_1, false));
map(0x78, MINECRAFT_26_1, false),
map(0x7A, MINECRAFT_26_3, false));
clientbound.register(
SystemChatPacket.class,
SystemChatPacket::new,
@@ -784,7 +786,8 @@ public enum StateRegistry {
map(0x73, MINECRAFT_1_21_2, true),
map(0x72, MINECRAFT_1_21_5, true),
map(0x77, MINECRAFT_1_21_9, true),
map(0x79, MINECRAFT_26_1, true));
map(0x79, MINECRAFT_26_1, true),
map(0x7C, MINECRAFT_26_3, true));
clientbound.register(
PlayerChatCompletionPacket.class,
PlayerChatCompletionPacket::new,
@@ -807,7 +810,8 @@ public enum StateRegistry {
map(0x50, MINECRAFT_1_21_2, false),
map(0x4F, MINECRAFT_1_21_5, false),
map(0x54, MINECRAFT_1_21_9, false),
map(0x56, MINECRAFT_26_1, false));
map(0x56, MINECRAFT_26_1, false),
map(0x58, MINECRAFT_26_3, false));
clientbound.register(
StartUpdatePacket.class,
() -> StartUpdatePacket.INSTANCE,
@@ -817,7 +821,8 @@ public enum StateRegistry {
map(0x70, MINECRAFT_1_21_2, false),
map(0x6F, MINECRAFT_1_21_5, false),
map(0x74, MINECRAFT_1_21_9, false),
map(0x76, MINECRAFT_26_1, false));
map(0x76, MINECRAFT_26_1, false),
map(0x78, MINECRAFT_26_3, false));
clientbound.register(
BundleDelimiterPacket.class,
() -> BundleDelimiterPacket.INSTANCE,
@@ -828,21 +833,46 @@ public enum StateRegistry {
map(0x73, MINECRAFT_1_20_5, false),
map(0x7A, MINECRAFT_1_21_2, false),
map(0x7F, MINECRAFT_1_21_9, false),
map(0x81, MINECRAFT_26_1, false));
map(0x81, MINECRAFT_26_1, false),
map(0x84, MINECRAFT_26_3, false));
clientbound.register(
ClientboundCustomReportDetailsPacket.class,
ClientboundCustomReportDetailsPacket::new,
map(0x7A, MINECRAFT_1_21, false),
map(0x81, MINECRAFT_1_21_2, false),
map(0x86, MINECRAFT_1_21_9, false),
map(0x88, MINECRAFT_26_1, false));
map(0x88, MINECRAFT_26_1, false),
map(0x8B, MINECRAFT_26_3, false));
clientbound.register(
ClientboundServerLinksPacket.class,
ClientboundServerLinksPacket::new,
map(0x7B, MINECRAFT_1_21, false),
map(0x82, MINECRAFT_1_21_2, false),
map(0x87, MINECRAFT_1_21_9, false),
map(0x89, MINECRAFT_26_1, false),
map(0x8C, MINECRAFT_26_3, false));
map(0x89, MINECRAFT_26_1, false));
clientbound.register(
UpdateTeamsPacket.class,
UpdateTeamsPacket::new,
map(0x41, MINECRAFT_1_9, true),
map(0x43, MINECRAFT_1_12, true),
map(0x44, MINECRAFT_1_12_1, true),
map(0x47, MINECRAFT_1_13, true),
map(0x4B, MINECRAFT_1_14, true),
map(0x4C, MINECRAFT_1_15, true),
map(0x55, MINECRAFT_1_17, true),
map(0x58, MINECRAFT_1_19_1, true),
map(0x56, MINECRAFT_1_19_3, true),
map(0x5A, MINECRAFT_1_19_4, true),
map(0x5C, MINECRAFT_1_20_2, true),
map(0x5E, MINECRAFT_1_20_3, true),
map(0x60, MINECRAFT_1_20_5, true),
map(0x67, MINECRAFT_1_21_2, true),
map(0x6B, MINECRAFT_1_21_9, true),
map(0x63, MINECRAFT_1_21_11, true),
map(0x6D, MINECRAFT_26_1, true),
map(0x6D, MINECRAFT_26_2, true));
}
},
LOGIN {
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2018-2026 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 final class ClientboundPostEffectsPacket implements MinecraftPacket {
private Key[] postEffects;
public ClientboundPostEffectsPacket() {
}
public ClientboundPostEffectsPacket(Key[] postEffects) {
this.postEffects = postEffects;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.postEffects = ProtocolUtils.readKeyArray(buf);
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeKeyArray(buf, this.postEffects);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}
@@ -32,7 +32,7 @@ public class JoinGamePacket implements MinecraftPacket {
private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(4 * 1024 * 1024);
private int entityId;
private short gamemode;
private int gamemode;
private int dimension;
private long partialHashedSeed; // 1.15+
private short difficulty;
@@ -47,7 +47,7 @@ public class JoinGamePacket implements MinecraftPacket {
private CompoundBinaryTag registry; // 1.16+
private DimensionInfo dimensionInfo; // 1.16+
private CompoundBinaryTag currentDimensionData; // 1.16.2+
private short previousGamemode; // 1.16+
private int previousGamemode; // 1.16+
private int simulationDistance; // 1.18+
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
private int portalCooldown; // 1.20+
@@ -63,11 +63,11 @@ public class JoinGamePacket implements MinecraftPacket {
this.entityId = entityId;
}
public short getGamemode() {
public int getGamemode() {
return gamemode;
}
public void setGamemode(short gamemode) {
public void setGamemode(int gamemode) {
this.gamemode = gamemode;
}
@@ -131,11 +131,11 @@ public class JoinGamePacket implements MinecraftPacket {
this.dimensionInfo = dimensionInfo;
}
public short getPreviousGamemode() {
public int getPreviousGamemode() {
return previousGamemode;
}
public void setPreviousGamemode(short previousGamemode) {
public void setPreviousGamemode(int previousGamemode) {
this.previousGamemode = previousGamemode;
}
@@ -345,8 +345,13 @@ public class JoinGamePacket implements MinecraftPacket {
String levelName = ProtocolUtils.readString(buf);
this.partialHashedSeed = buf.readLong();
this.gamemode = buf.readByte();
this.previousGamemode = buf.readByte();
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
this.gamemode = ProtocolUtils.readVarInt(buf);
this.previousGamemode = ProtocolUtils.readVarInt(buf); // game mode + 1 or 0
} else {
this.gamemode = buf.readByte();
this.previousGamemode = buf.readByte(); // game mode or -1
}
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
@@ -498,8 +503,13 @@ public class JoinGamePacket implements MinecraftPacket {
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
buf.writeLong(partialHashedSeed);
buf.writeByte(gamemode);
buf.writeByte(previousGamemode);
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
ProtocolUtils.writeVarInt(buf, this.gamemode);
ProtocolUtils.writeVarInt(buf, this.previousGamemode);
} else {
buf.writeByte(this.gamemode);
buf.writeByte(this.previousGamemode);
}
buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat());
@@ -33,11 +33,11 @@ public class RespawnPacket implements MinecraftPacket {
private int dimension;
private long partialHashedSeed;
private short difficulty;
private short gamemode;
private int gamemode;
private String levelType = "";
private byte dataToKeep; // 1.16+
private DimensionInfo dimensionInfo; // 1.16-1.16.1
private short previousGamemode; // 1.16+
private int previousGamemode; // 1.16+
private CompoundBinaryTag currentDimensionData; // 1.16.2+
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
private int portalCooldown; // 1.20+
@@ -46,9 +46,9 @@ public class RespawnPacket implements MinecraftPacket {
public RespawnPacket() {
}
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode,
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, int gamemode,
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
short previousGamemode, CompoundBinaryTag currentDimensionData,
int previousGamemode, CompoundBinaryTag currentDimensionData,
@Nullable Pair<String, Long> lastDeathPosition, int portalCooldown,
int seaLevel) {
this.dimension = dimension;
@@ -97,11 +97,11 @@ public class RespawnPacket implements MinecraftPacket {
this.difficulty = difficulty;
}
public short getGamemode() {
public int getGamemode() {
return gamemode;
}
public void setGamemode(short gamemode) {
public void setGamemode(int gamemode) {
this.gamemode = gamemode;
}
@@ -121,11 +121,11 @@ public class RespawnPacket implements MinecraftPacket {
this.dataToKeep = dataToKeep;
}
public short getPreviousGamemode() {
public int getPreviousGamemode() {
return previousGamemode;
}
public void setPreviousGamemode(short previousGamemode) {
public void setPreviousGamemode(int previousGamemode) {
this.previousGamemode = previousGamemode;
}
@@ -197,9 +197,17 @@ public class RespawnPacket implements MinecraftPacket {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
this.partialHashedSeed = buf.readLong();
}
this.gamemode = buf.readByte();
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
this.gamemode = ProtocolUtils.readVarInt(buf);
} else {
this.gamemode = buf.readByte();
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
this.previousGamemode = buf.readByte();
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
this.previousGamemode = ProtocolUtils.readVarInt(buf);
} else {
this.previousGamemode = buf.readByte();
}
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
this.dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version);
@@ -249,9 +257,17 @@ public class RespawnPacket implements MinecraftPacket {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
buf.writeLong(partialHashedSeed);
}
buf.writeByte(gamemode);
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
ProtocolUtils.writeVarInt(buf, this.gamemode);
} else {
buf.writeByte(this.gamemode);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
buf.writeByte(previousGamemode);
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) {
ProtocolUtils.writeVarInt(buf, this.previousGamemode);
} else {
buf.writeByte(this.previousGamemode);
}
buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat());
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
@@ -0,0 +1,197 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero 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 io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
import java.util.List;
public class UpdateTeamsPacket implements MinecraftPacket {
protected String name;
protected Mode mode;
protected Component displayName;
protected Component prefix;
protected Component suffix;
protected NameTagVisibility nameTagVisibility;
protected CollisionRule collisionRule;
protected int color;
protected byte friendlyFlags;
protected List<String> players;
public UpdateTeamsPacket(String name, Mode mode, Component displayName, Component prefix, Component suffix, NameTagVisibility nameTagVisibility, CollisionRule collisionRule, int color, byte friendlyFlags, List<String> players) {
this.name = name;
this.mode = mode;
this.displayName = displayName;
this.prefix = prefix;
this.suffix = suffix;
this.nameTagVisibility = nameTagVisibility;
this.collisionRule = collisionRule;
this.color = color;
this.friendlyFlags = friendlyFlags;
this.players = players;
}
public UpdateTeamsPacket() {
}
@Override
public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException("Packet is not implemented");
}
@Override
public boolean handle(MinecraftSessionHandler minecraftSessionHandler) {
return false;
}
@Override
public void encode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException("Packet is not implemented");
}
public enum Mode {
CREATE,
REMOVE,
UPDATE,
ADD_PLAYER,
REMOVE_PLAYER,
}
public enum NameTagVisibility {
ALWAYS("always"),
NEVER("never"),
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
HIDE_FOR_OWN_TEAM("hideForOwnTeam");
private final String value;
NameTagVisibility(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public enum CollisionRule {
ALWAYS("always"),
NEVER("never"),
PUSH_OTHER_TEAMS("pushOtherTeams"),
PUSH_OWN_TEAM("pushOwnTeam");
private final String value;
CollisionRule(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public String getName() {
return name;
}
public Mode getMode() {
return mode;
}
public Component getDisplayName() {
return displayName;
}
public Component getPrefix() {
return prefix;
}
public Component getSuffix() {
return suffix;
}
public NameTagVisibility getNameTagVisibility() {
return nameTagVisibility;
}
public CollisionRule getCollisionRule() {
return collisionRule;
}
public int getColor() {
return color;
}
public byte getFriendlyFlags() {
return friendlyFlags;
}
public List<String> getPlayers() {
return players;
}
public void setName(String name) {
this.name = name;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public void setDisplayName(Component displayName) {
this.displayName = displayName;
}
public void setPrefix(Component prefix) {
this.prefix = prefix;
}
public void setSuffix(Component suffix) {
this.suffix = suffix;
}
public void setNameTagVisibility(NameTagVisibility nameTagVisibility) {
this.nameTagVisibility = nameTagVisibility;
}
public void setCollisionRule(CollisionRule collisionRule) {
this.collisionRule = collisionRule;
}
public void setColor(int color) {
this.color = color;
}
public void setFriendlyFlags(byte friendlyFlags) {
this.friendlyFlags = friendlyFlags;
}
public void setPlayers(List<String> players) {
this.players = players;
}
}
@@ -25,6 +25,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_6;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_3;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
@@ -273,16 +274,21 @@ public class ArgumentPropertyRegistry {
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_21_6, 51), mapSet(MINECRAFT_1_21_5, 50), mapSet(MINECRAFT_1_20_3, 49),
mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54),mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48),
mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16
empty(id("minecraft:uuid", mapSet(MINECRAFT_26_3, 61), mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54), mapSet(MINECRAFT_1_20_5, 53),
mapSet(MINECRAFT_1_20_3, 48), mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16
empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_6, 52), mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50)));
empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_21_6, 53), mapSet(MINECRAFT_1_21_5, 52), mapSet(MINECRAFT_1_20_5, 51)));
empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_21_6, 54), mapSet(MINECRAFT_1_21_5, 53), mapSet(MINECRAFT_1_20_5, 52)));
empty(id("minecraft:hex_color", mapSet(MINECRAFT_1_21_6, 17))); // added in 1.21.6
empty(id("minecraft:dialog", mapSet(MINECRAFT_1_21_6, 55))); // added in 1.21.6
empty(id("minecraft:dialog", mapSet(MINECRAFT_26_3, 58), mapSet(MINECRAFT_1_21_6, 55))); // added in 1.21.6
empty(id("minecraft:team_color", mapSet(MINECRAFT_26_2, 16))); // renamed from color in 26.2
empty(id("minecraft:context_float_provider", mapSet(MINECRAFT_26_3, 55))); // added in 26.3
empty(id("minecraft:context_int_provider", mapSet(MINECRAFT_26_3, 56))); // added in 26.3
empty(id("minecraft:slot_source", mapSet(MINECRAFT_26_3, 57))); // added in 26.3
empty(id("minecraft:feature", mapSet(MINECRAFT_26_3, 59))); // added in 26.3
empty(id("minecraft:swing_animation", mapSet(MINECRAFT_26_3, 60))); // added in 26.3
// Crossstitch support
register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD);
@@ -116,6 +116,8 @@ public class KeyedPlayerChatPacket implements MinecraftPacket {
ProtocolUtils.readByteArray(buf));
}
}
unsigned = true;
}
@Override
@@ -132,6 +132,7 @@ public class KeyedPlayerCommandPacket implements MinecraftPacket {
unsigned = true;
}
unsigned = true;
}
@Override
@@ -69,6 +69,7 @@ public class SessionPlayerChatPacket implements MinecraftPacket {
this.salt = buf.readLong();
this.signed = buf.readBoolean();
if (this.signed) {
this.signed = false;
this.signature = readMessageSignature(buf);
} else {
this.signature = new byte[0];
@@ -47,6 +47,8 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
this.salt = buf.readLong();
this.argumentSignatures = new ArgumentSignatures(buf);
this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion);
this.argumentSignatures = new ArgumentSignatures();
}
@Override
@@ -30,40 +30,51 @@ package com.velocitypowered.proxy.util;
* <p>This class is not thread-safe. If multiple threads access an instance concurrently,
* external synchronization is required.</p>
*/
@SuppressWarnings("checkstyle:WhitespaceAfter") // Not our class
public final class IntervalledCounter {
private static final int INITIAL_SIZE = 8;
/**
* Data points within this many nanoseconds of the newest one are merged into it, bounding the
* number of stored data points to roughly {@code interval / COALESCE_INTERVAL}.
*/
private static final long COALESCE_INTERVAL = 1_000_000L; // 1ms
/**
* Ring buffer holding the timestamp (in nanoseconds) for each data point.
*/
protected long[] times;
private long[] times;
/**
* Ring buffer holding the count associated with each timestamp.
*/
protected long[] counts;
private long[] counts;
/**
* The sliding window size in nanoseconds. Only entries with time >= (currentTime - interval)
* are considered part of the window.
*/
protected final long interval;
private final long interval;
/**
* Cached lower bound of the window (in nanoseconds) after the last update.
*/
protected long minTime;
private long minTime;
/**
* Running sum of all counts currently within the window.
*/
protected long sum;
private long sum;
/**
* Head index (inclusive) of the ring buffer.
*/
protected int head; // inclusive
private int head; // inclusive
/**
* Tail index (exclusive) of the ring buffer.
*/
protected int tail; // exclusive
private int tail; // exclusive
/**
* Creates a new counter with the specified interval.
@@ -131,6 +142,8 @@ public final class IntervalledCounter {
/**
* Adds {@code count} units at the specified timestamp, assuming the timestamp is within the
* current window. If the timestamp is older than {@code minTime}, the value is ignored.
* If the timestamp is within {@link #COALESCE_INTERVAL} of the newest stored data point, the
* count is merged into that data point instead of creating a new one.
* This method does not automatically advance the window; callers should invoke
* {@link #updateCurrentTime()} or {@link #updateCurrentTime(long)} beforehand.
*
@@ -142,6 +155,15 @@ public final class IntervalledCounter {
if (currTime - this.minTime < 0) {
return;
}
if (this.head != this.tail) {
final int last = this.tail == 0 ? this.times.length - 1 : this.tail - 1;
// guard against overflow by using subtraction
if (currTime - this.times[last] < COALESCE_INTERVAL) {
this.counts[last] += count;
this.sum += count;
return;
}
}
int nextTail = (this.tail + 1) % this.times.length;
if (nextTail == this.head) {
this.resize();
@@ -219,7 +241,7 @@ public final class IntervalledCounter {
* @return the rate in units per second for the current window
*/
public double getRate() {
return (double)this.sum / ((double)this.interval * 1.0E-9);
return (double) this.sum / ((double) this.interval * 1.0E-9);
}
/**
@@ -0,0 +1,115 @@
/*
* Copyright (C) 2026 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.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
class IntervalledCounterTest {
private static final long INTERVAL = TimeUnit.SECONDS.toNanos(7);
@Test
void sumAndRateTrackAddedCounts() {
IntervalledCounter counter = new IntervalledCounter(INTERVAL);
long now = 0;
counter.updateAndAdd(700, now);
assertEquals(700, counter.getSum());
assertEquals(100.0, counter.getRate(), 1e-9);
now += TimeUnit.SECONDS.toNanos(1);
counter.updateAndAdd(1400, now);
assertEquals(2100, counter.getSum());
assertEquals(300.0, counter.getRate(), 1e-9);
}
@Test
void dataPointsOutsideTheWindowAreEvicted() {
IntervalledCounter counter = new IntervalledCounter(INTERVAL);
long now = 0;
counter.updateAndAdd(10, now);
now += TimeUnit.SECONDS.toNanos(3);
counter.updateAndAdd(20, now);
assertEquals(30, counter.getSum());
assertEquals(2, counter.totalDataPoints());
// 7.5s after the first point: only the second point remains
now = TimeUnit.MILLISECONDS.toNanos(7500);
counter.updateCurrentTime(now);
assertEquals(20, counter.getSum());
assertEquals(1, counter.totalDataPoints());
// 10.5s: everything has expired
now = TimeUnit.MILLISECONDS.toNanos(10500);
counter.updateCurrentTime(now);
assertEquals(0, counter.getSum());
assertEquals(0, counter.totalDataPoints());
}
@Test
void storedDataPointsAreBoundedByTimeNotByCallCount() {
IntervalledCounter counter = new IntervalledCounter(INTERVAL);
// Simulate a flood of tiny (even zero-sized) packets arriving far faster than one per
// millisecond for the whole window. Before coalescing was introduced, each call stored a
// separate data point, so an attacker could grow the ring buffer without bound while staying
// under a bytes-per-second limit.
final long stepNanos = 100;
final long calls = INTERVAL / stepNanos;
long now = 0;
for (long i = 0; i < calls; i++) {
counter.updateAndAdd(i % 2, now);
now += stepNanos;
}
long expectedSum = calls / 2;
assertEquals(expectedSum, counter.getSum());
// 7s window at 1ms coalescing granularity is ~7000 points; leave some slack.
assertTrue(counter.totalDataPoints() <= 7100,
"expected at most ~7000 data points, got " + counter.totalDataPoints());
assertTrue(counter.totalDataPoints() >= 7000,
"expected at least 7000 data points, got " + counter.totalDataPoints());
}
@Test
void coalescedDataPointsExpireTogether() {
IntervalledCounter counter = new IntervalledCounter(INTERVAL);
long now = 0;
counter.updateAndAdd(5, now);
// Within the same 1ms bucket: merged into the previous point
counter.updateAndAdd(7, now + 500_000L);
assertEquals(12, counter.getSum());
assertEquals(1, counter.totalDataPoints());
// A new bucket starts a new point
counter.updateAndAdd(1, now + 1_000_000L);
assertEquals(13, counter.getSum());
assertEquals(2, counter.totalDataPoints());
// The merged bucket carries the timestamp of its first point and expires with it
counter.updateCurrentTime(now + INTERVAL + 1);
assertEquals(1, counter.getSum());
assertEquals(1, counter.totalDataPoints());
}
}
+9
View File
@@ -0,0 +1,9 @@
build:
- "./gradlew build -x check -x javadoc --no-daemon"
artifacts:
"/jars/Velocity.jar": "proxy/build/libs/velocity-proxy-4.1.0-SNAPSHOT-all.jar"
release:
- "mvn deploy:deploy-file -DgroupId=de.steamwar -DartifactId=velocity -Dversion=RELEASE -Dpackaging=jar -Dfile=/jars/Velocity.jar -Durl=file:///var/www/maven/"