Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
567531e
Add secure single-port HTTP proxy transport
BenCodez Aug 31, 2026
1fd3be9
Validate HTTP client certificates at TLS boundary
BenCodez Aug 31, 2026
d27636b
Harden HTTP transport lifecycle
BenCodez Aug 31, 2026
1d30c94
Address HTTP transport review findings
BenCodez Aug 31, 2026
e9bbe03
Harden HTTP shutdown and protocol parsing
BenCodez Aug 31, 2026
38a37dd
fix(http): retain votes rejected by transport queue
BenCodez Aug 31, 2026
5722685
fix(http): persist outbound deliveries until ack
BenCodez Aug 31, 2026
aa0f8d8
fix(http): persist poll-created backend queues
BenCodez Aug 31, 2026
63e8f9d
fix(http): validate enrollment codes in Control
BenCodez Aug 31, 2026
5b666dc
fix(http): serialize callbacks and renew transport CA
BenCodez Sep 1, 2026
dbf416f
fix(http): verify backend presence against proxy route
BenCodez Sep 1, 2026
ac6a947
fix(http): preserve proxy callback order
BenCodez Sep 1, 2026
f104e61
fix(http): preserve callback order and crash dedup
BenCodez Sep 1, 2026
13fd2b7
fix(http): normalize corrupt fence errors
BenCodez Sep 1, 2026
71f5ca4
fix(http): revoke pending enrollment codes
BenCodez Sep 1, 2026
6216a04
fix(http): distinguish incomplete inbound deliveries
BenCodez Sep 1, 2026
2a9fcc6
test(http): import filesystem helper
BenCodez Sep 1, 2026
9eae8b5
fix(http): bound responses and persist queue roots
BenCodez Sep 1, 2026
b13ce28
test(http): await resumed delivery acknowledgement
BenCodez Sep 1, 2026
0a938cf
fix(http): persist transport directory entries
BenCodez Sep 1, 2026
5949e11
fix(http): coordinate transport lifecycle handoffs
BenCodez Sep 1, 2026
dd1ef83
fix(ci): restore complete proxy source
BenCodez Sep 1, 2026
0e9cf5a
fix(test): disambiguate transport validation assertion
BenCodez Sep 1, 2026
f81aa90
fix(http): validate readiness off the server thread
BenCodez Sep 1, 2026
b7503b9
fix(control): cancel stale transport publication
BenCodez Sep 1, 2026
67e9d12
fix(http): open proxy listener after runtime setup
BenCodez Sep 1, 2026
425664b
Restore HTTP transport after failed control reload
BenCodez Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion VotingPlugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<velocity.version>3.4.0</velocity.version>
<bouncycastle.version>1.85</bouncycastle.version>
</properties>
<build>
<resources>
Expand Down Expand Up @@ -151,6 +152,10 @@
<shadedPattern>
${project.groupId}.votingplugin.bstats</shadedPattern>
</relocation>
<relocation>
<pattern>org.bouncycastle</pattern>
<shadedPattern>${project.groupId}.votingplugin.bouncycastle</shadedPattern>
</relocation>
<relocation>
<pattern>xyz.upperlevel.spigot</pattern>
<shadedPattern>
Expand Down Expand Up @@ -254,6 +259,16 @@
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
Expand Down Expand Up @@ -692,4 +707,4 @@
</build>
</profile>
</profiles>
</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -1214,36 +1214,94 @@ public String getBackendHostedControlStatus() {
}

/** Recreates proxy transports after Control applies BungeeSettings.yml. */
public synchronized void restartBackendProxyHandler() {
public void restartBackendProxyHandler() {
restartBackendProxyHandler(System.nanoTime() + TimeUnit.SECONDS.toNanos(25));
}

/** Recreates proxy transports while preserving the caller's end-to-end validation deadline. */
public void restartBackendProxyHandler(long validationDeadlineNanos) {
BackendProxyRestart restart = prepareBackendProxyHandlerRestart();
try {
validateBackendProxyHandlerRestart(restart, validationDeadlineNanos);
completeBackendProxyHandlerRestart(restart);
} catch (RuntimeException failure) {
abortBackendProxyHandlerRestart(restart);
throw failure;
}
}

/** Prepared on the Bukkit thread, validated off-thread, then atomically published on Bukkit. */
public static final class BackendProxyRestart {
private final BackendProxyHandler previous;
private final BackendProxyHandler replacement;
private final boolean disabled;
private final boolean previousPrepared;
private boolean finished;

private BackendProxyRestart(BackendProxyHandler previous, BackendProxyHandler replacement, boolean disabled,
boolean previousPrepared) {
this.previous = previous;
this.replacement = replacement;
this.disabled = disabled;
this.previousPrepared = previousPrepared;
}
}

public synchronized BackendProxyRestart prepareBackendProxyHandlerRestart() {
BackendProxyHandler previous = backendProxyHandler;
if (!bungeeSettings.isUseBungeecoord()) {
backendProxyHandler = null;
if (previous != null) previous.close();
BackendControlAutoEnrollment enrollment = backendControlAutoEnrollment;
backendControlAutoEnrollment = null;
if (enrollment != null) enrollment.close();
return;
return new BackendProxyRestart(previous, null, true, false);
}
BungeeMethod replacementMethod = BungeeMethod.getByName(bungeeSettings.getBungeeMethod());
if (previous != null) previous.prepareForReplacement(replacementMethod);
boolean previousPrepared = previous != null && previous.prepareForReplacement(replacementMethod);
BackendProxyHandler replacement = new BackendProxyHandler(this, backendProcessedVoteCache);
try {
replacement.load();
replacement.validateTransport();
if (previous != null) previous.completeRedisHandoff(replacement);
} catch (RuntimeException failure) {
replacement.close();
if (previousPrepared) previous.restoreAfterFailedReplacement();
throw failure;
}
backendProxyHandler = replacement;
if (previous != null) previous.close();
return new BackendProxyRestart(previous, replacement, false, previousPrepared);
}

public void validateBackendProxyHandlerRestart(BackendProxyRestart restart, long validationDeadlineNanos) {
if (restart == null) throw new IllegalArgumentException("Backend proxy restart is required");
if (restart.replacement != null) restart.replacement.validateTransport(validationDeadlineNanos);
}

public synchronized void completeBackendProxyHandlerRestart(BackendProxyRestart restart) {
if (restart == null || restart.finished) throw new IllegalStateException("Backend proxy restart is no longer active");
if (backendProxyHandler != restart.previous) throw new IllegalStateException("Backend proxy handler changed during restart");
if (restart.disabled) {
backendProxyHandler = null;
if (restart.previous != null) restart.previous.close();
BackendControlAutoEnrollment enrollment = backendControlAutoEnrollment;
backendControlAutoEnrollment = null;
if (enrollment != null) enrollment.close();
restart.finished = true;
return;
}
if (restart.previous != null) restart.previous.completeRedisHandoff(restart.replacement);
backendProxyHandler = restart.replacement;
if (restart.previous != null) restart.previous.close();
restart.finished = true;
try {
refreshBackendControlAutoEnrollment();
} catch (IOException e) {
getLogger().warning("[Control] Automatic backend enrollment was not refreshed: " + e.getMessage());
}
}

public synchronized void abortBackendProxyHandlerRestart(BackendProxyRestart restart) {
if (restart == null || restart.finished) return;
if (restart.replacement != null) restart.replacement.close();
Comment thread
BenCodez marked this conversation as resolved.
if (restart.previousPrepared && backendProxyHandler == restart.previous) {
restart.previous.restoreAfterFailedReplacement();
}
restart.finished = true;
}

/** Keeps one plugin-message listener for the plugin lifetime and atomically swaps its active backend handler. */
public synchronized void activateBackendPluginMessageHandler(GlobalMessageHandler target) {
backendPluginMessageTarget.set(target);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,30 @@ public void close() {
}

/** Releases a same-method subscriber/listener before its replacement starts. */
public void prepareForReplacement(BungeeMethod replacementMethod) {
public boolean prepareForReplacement(BungeeMethod replacementMethod) {
if (method == replacementMethod && method != BungeeMethod.PLUGINMESSAGING && method != BungeeMethod.REDIS) {
transportManager.prepareForReplacement();
return method == BungeeMethod.HTTP;
}
return false;
}

/** Restores a prepared HTTP transport when its replacement fails validation. */
public void restoreAfterFailedReplacement() {
transportManager.restorePreparedTransport();
}

/** Fails a configuration apply when its selected transport did not initialize. */
public void validateTransport() {
validateTransport(System.nanoTime() + java.util.concurrent.TimeUnit.SECONDS.toNanos(25));
}

/** Validates transport startup without extending the caller's existing deadline. */
public void validateTransport(long deadlineNanos) {
if (method == null || globalMessageHandler == null || presenceManager == null) {
throw new IllegalStateException("Backend proxy handler initialization failed");
}
transportManager.validate();
transportManager.validate(deadlineNanos);
}

/** Completes the no-loss/no-duplicate same-Redis subscriber handoff after validation. */
Expand Down
Loading
Loading