Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
# VotifierPlus
Fork of votifier

## Optional Network Doctor diagnostics

VotifierPlus exposes a read-only `getNetworkHealthSnapshot()` method on its
Bukkit, BungeeCord, and Velocity entry points for compatible management
software. The immutable snapshot reports whether the provider is present, the
listener is initialized, whether forwarding state is known, and the names of
enabled forwarding destinations. Destination names are bounded and
deduplicated; hosts, ports, keys, tokens, credentials, and configuration text
are never included. The API is optional and does not participate in vote
receipt, event dispatch, or forwarding, so older VotingPlugin and Control
nodes continue to work without it.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
import com.vexsoftware.votifier.crypto.RSAIO;
import com.vexsoftware.votifier.crypto.RSAKeygen;
import com.vexsoftware.votifier.crypto.TokenUtil;
import com.vexsoftware.votifier.diagnostic.VotifierDiagnostics;
import com.vexsoftware.votifier.diagnostic.VotifierDiagnosticsSnapshot;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.model.VotifierEvent;
import com.vexsoftware.votifier.net.ThrottleConfig;
Expand All @@ -72,6 +74,11 @@
*/
public class VotifierPlus extends JavaPlugin {

/** Optional read-only management-plane snapshot; never participates in vote processing. */
public VotifierDiagnosticsSnapshot getNetworkHealthSnapshot() {
return VotifierDiagnostics.snapshot(voteReceiver);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Safely publish receiver replacements to diagnostic callers

When management software calls this accessor from a thread other than the platform reload thread, the unsynchronized, non-volatile voteReceiver read is not guaranteed to observe the replacement written by reload(). It can therefore keep snapshotting the old closed receiver and report the listener as unavailable after the new listener is healthy; the same data race exists in the added BungeeCord and Velocity accessors. Publish each receiver field safely (for example, make it volatile) so post-reload snapshots observe the current receiver.

Useful? React with 👍 / 👎.

}

/** The Votifier instance. */
private static VotifierPlus instance;

Expand All @@ -83,7 +90,7 @@ public class VotifierPlus extends JavaPlugin {
private Updater updater;

/** The vote receiver. */
private VoteReceiver voteReceiver;
private volatile VoteReceiver voteReceiver;

/** The RSA key pair. */
@Setter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
import java.util.Set;

import com.vexsoftware.votifier.ForwardServer;
import com.vexsoftware.votifier.crypto.RSAIO;
import com.vexsoftware.votifier.crypto.RSAKeygen;
import com.vexsoftware.votifier.crypto.TokenUtil;
import com.vexsoftware.votifier.crypto.RSAIO;
import com.vexsoftware.votifier.crypto.RSAKeygen;
import com.vexsoftware.votifier.crypto.TokenUtil;
import com.vexsoftware.votifier.diagnostic.VotifierDiagnostics;
import com.vexsoftware.votifier.diagnostic.VotifierDiagnosticsSnapshot;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.ThrottleConfig;
import com.vexsoftware.votifier.net.VoteReceiver;
Expand All @@ -30,10 +32,15 @@
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;

public class VotifierPlusBungee extends Plugin {
public class VotifierPlusBungee extends Plugin {

/** Optional read-only management-plane snapshot; never participates in vote processing. */
public VotifierDiagnosticsSnapshot getNetworkHealthSnapshot() {
return VotifierDiagnostics.snapshot(voteReceiver);
}
private VotifierPlusBungee instance;
@Getter
private VoteReceiver voteReceiver;
private volatile VoteReceiver voteReceiver;
@Getter
private Config config;
@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.vexsoftware.votifier.diagnostic;

import java.util.ArrayList;
import java.util.List;

import com.vexsoftware.votifier.ForwardServer;
import com.vexsoftware.votifier.net.VoteReceiver;

/** Optional adapter used by management software without a hard dependency. */
public final class VotifierDiagnostics {
private VotifierDiagnostics() {
}

/**
* Captures only state already owned by the receiver. A null receiver means
* that listener state is unavailable, rather than falsely healthy.
*/
public static VotifierDiagnosticsSnapshot snapshot(VoteReceiver receiver) {
if (receiver == null) {
return new VotifierDiagnosticsSnapshot(Boolean.TRUE, null, null, null);
}
List<String> destinations = new ArrayList<String>();
boolean forwardingKnown = true;
try {
for (String name : receiver.getServers()) {
if (name == null || name.trim().isEmpty()) {
// An unrepresentable configured entry means the forwarding inventory is incomplete.
forwardingKnown = false;
continue;
}
ForwardServer server = receiver.getServerData(name);
if (server == null) {
// A configured entry that cannot be classified is incomplete evidence.
forwardingKnown = false;
} else if (server.isEnabled()) {
if (name.length() > VotifierDiagnosticsSnapshot.MAX_NAME_LENGTH
|| destinations.size() >= VotifierDiagnosticsSnapshot.MAX_DESTINATIONS) {
// Do not claim complete evidence after the bounded snapshot omitted a risk.
forwardingKnown = false;
continue;
}
// Preserve configured identity; snapshot validation downgrades noncanonical names.
destinations.add(name);
}
}
} catch (RuntimeException unavailable) {
forwardingKnown = false;
destinations.clear();
}
return new VotifierDiagnosticsSnapshot(Boolean.TRUE,
receiver.getServer() != null && !receiver.getServer().isClosed(),
forwardingKnown, destinations);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.vexsoftware.votifier.diagnostic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
* Bounded, read-only Votifier state for optional management-plane diagnostics.
*
* <p>This type deliberately contains destination names only. It does not expose
* forwarding endpoints, ports, keys, tokens, or configuration text.</p>
*/
public final class VotifierDiagnosticsSnapshot {
public static final int MAX_DESTINATIONS = 100;
public static final int MAX_NAME_LENGTH = 80;

private final Boolean providerPresent;
private final Boolean listenerInitialized;
private final Boolean forwardingKnown;
private final List<String> forwardingDestinations;

public VotifierDiagnosticsSnapshot(Boolean providerPresent, Boolean listenerInitialized,
Boolean forwardingKnown, Iterable<String> forwardingDestinations) {
this.providerPresent = providerPresent;
this.listenerInitialized = listenerInitialized;
BoundedNames bounded = boundedNames(forwardingDestinations);
this.forwardingKnown = forwardingKnown == null || !Boolean.TRUE.equals(forwardingKnown)
? forwardingKnown : bounded.complete ? Boolean.TRUE : Boolean.FALSE;
this.forwardingDestinations = bounded.names;
}

public Boolean getProviderPresent() {
return providerPresent;
}

public Boolean getListenerInitialized() {
return listenerInitialized;
}

public Boolean getForwardingKnown() {
return forwardingKnown;
}

public List<String> getForwardingDestinations() {
return forwardingDestinations;
}

private static BoundedNames boundedNames(Iterable<String> values) {
if (values == null) {
return new BoundedNames(Collections.emptyList(), true);
}
Set<String> unique = new LinkedHashSet<String>();
boolean complete = true;
for (String value : values) {
if (value == null) {
complete = false;
continue;
}
String name = value.trim();
if (name.isEmpty() || name.length() > MAX_NAME_LENGTH) {
complete = false;
continue;
}
if (!name.equals(value) || name.codePoints().anyMatch(Character::isISOControl)) {
complete = false;
continue;
}
unique.add(name);
if (unique.size() > MAX_DESTINATIONS) {
complete = false;
unique.remove(name);
}
}
return new BoundedNames(Collections.unmodifiableList(new ArrayList<String>(unique)), complete);
}

private static final class BoundedNames {
private final List<String> names;
private final boolean complete;

private BoundedNames(List<String> names, boolean complete) {
this.names = names;
this.complete = complete;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import com.vexsoftware.votifier.ForwardServer;
import com.vexsoftware.votifier.crypto.RSAIO;
import com.vexsoftware.votifier.crypto.RSAKeygen;
import com.vexsoftware.votifier.crypto.TokenUtil;
import com.vexsoftware.votifier.crypto.RSAIO;
import com.vexsoftware.votifier.crypto.RSAKeygen;
import com.vexsoftware.votifier.crypto.TokenUtil;
import com.vexsoftware.votifier.diagnostic.VotifierDiagnostics;
import com.vexsoftware.votifier.diagnostic.VotifierDiagnosticsSnapshot;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.ThrottleConfig;
import com.vexsoftware.votifier.net.VoteReceiver;
Expand All @@ -44,9 +46,14 @@

@Plugin(id = "votifierplus", name = "VotifierPlus", version = "1.0", url = "https://www.spigotmc.org/resources/votifierplus.74040", description = "Votifier Velocity Version", authors = {
"BenCodez" })
public class VotifierPlusVelocity {
public class VotifierPlusVelocity {

/** Optional read-only management-plane snapshot; never participates in vote processing. */
public VotifierDiagnosticsSnapshot getNetworkHealthSnapshot() {
return VotifierDiagnostics.snapshot(voteReceiver);
}
@Getter
private VoteReceiver voteReceiver;
private volatile VoteReceiver voteReceiver;
@Getter
private Config config;
@Getter
Expand Down
Loading
Loading