Compare commits

..
3 Commits
Author SHA1 Message Date
Joe Hirschfeld eefd80005e More modifications, back to basics 2020-02-06 22:16:19 -08:00
Joe Hirschfeld 3a2cdfd7ea Refactor some stuff around
There are a _lot_ of changes, including a ton which segment dependencies
better. So thats cool
2020-02-03 23:42:25 -08:00
Joe Hirschfeld d0089c1c0c some stuff 2019-12-24 16:53:13 -08:00
181 changed files with 550 additions and 7887 deletions

No files matched your search

-73
View File
@@ -1,73 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
<!--
Thank you for submitting an issue for NuVotifier. We appreciate
any and all bug reports.
Before you continue, please ensure you are reporting
a real bug and are not suffering from a configuration issue
or a plugin problem. Once NuVotifier has received and
called your vote listeners, it's not our issue any more.
If you are having difficulty setting up NuVotifier to receive
votes, you are in the wrong place. Please view our wiki
at https://github.com/NuVotifier/NuVotifier/wiki for a
comprehensive set of instructions for configuring and
diagnosing common problems with NuVotifier.
If you are certain that there is a problem with
NuVotifier, please continue filling the form below.
Make sure to follow these instructions:
1. Tell the exact NuVotifier version you're using.
If you just say "latest" your issue will get closed.
2. Please include all relevant logs and configuration
files. Make sure to redact any tokens in the config
file.
3. Don't write anything between these arrows.
We won't see it otherwise.
4. Before you create your issue, delete this block of
text and the code block at the very bottom. -->
### Information
**Platform on which you run NuVotifier**: <!-- Either Bukkit, Sponge, BungeeCord, or Velocity. -->
**Version of NuVotifier**:
```
<!--
Getting this depends on your platform:
Bukkit: Use /version Votifier
Sponge: Use /sponge plugins and find NuVotifier
BungeeCord: No such command exists.
Velocity: Use /velocity plugins from in-game and hover over "nuvotifier"
-->
```
**Relevant log**: <!-- Paste the log on https://gist.github.com/, replace this with link -->
### Details
**Description**
<!-- Replace this with a brief summary of the bug. -->
**Steps to reproduce**
<!-- Replace this with exactly what you did to cause the bug. -->
**Expected behavior**
<!-- Replace this with what you expected to happen. -->
```
Please make sure to delete this line of text and the three ``` characters surrounding it, or your issue will be closed.
```
+1 -1
View File
@@ -6,7 +6,7 @@ Votifier will also support NuVotifier.
NuVotifier supports many different platforms using the same universal JAR:
+ Bukkit / Spigot / Paper >=1.7.10
+ Bukkit / Spigot / Paper >=1.8
+ Sponge 7
+ BungeeCord / Waterfall
+ Velocity
-4
View File
@@ -1,4 +0,0 @@
# nuvotifier-api
Files in this directory have a strong API promise - they really shouldn't
change over time. Feel free to depend against it!
-3
View File
@@ -1,3 +0,0 @@
dependencies {
implementation 'com.google.code.gson:gson:2.8.0'
}
@@ -1,229 +0,0 @@
/*
* Copyright (C) 2011 Vex Software LLC
* This file is part of Votifier.
*
* Votifier 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.
*
* Votifier 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 Votifier. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vexsoftware.votifier.model;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.Arrays;
import java.util.Base64;
/**
* A model for a vote.
*
* @author Blake Beaupain
*/
public class Vote {
/**
* The name of the vote service.
*/
private String serviceName;
/**
* The username of the voter.
*/
private String username;
/**
* The address of the voter.
*/
private String address;
/**
* The date and time of the vote.
*/
private String timeStamp;
private byte[] additionalData;
@Deprecated
public Vote() {
}
public Vote(String serviceName, String username, String address, String timeStamp) {
this.serviceName = serviceName;
this.username = username;
this.address = address;
this.timeStamp = timeStamp;
this.additionalData = null;
}
public Vote(String serviceName, String username, String address, String timeStamp, byte[] additionalData) {
this.serviceName = serviceName;
this.username = username;
this.address = address;
this.timeStamp = timeStamp;
this.additionalData = additionalData == null ? null : additionalData.clone();
}
public Vote(Vote vote) {
this(vote.getServiceName(), vote.getUsername(), vote.getAddress(), vote.getTimeStamp(),
vote.getAdditionalData() == null ? null : vote.getAdditionalData().clone());
}
private static String getTimestamp(JsonElement object) {
try {
return Long.toString(object.getAsLong());
} catch (Exception e) {
return object.getAsString();
}
}
public Vote(JsonObject jsonObject) {
this(jsonObject.get("serviceName").getAsString(),
jsonObject.get("username").getAsString(),
jsonObject.get("address").getAsString(),
getTimestamp(jsonObject.get("timestamp")));
if (jsonObject.has("additionalData"))
additionalData = Base64.getDecoder().decode(jsonObject.get("additionalData").getAsString());
}
@Override
public String toString() {
String data;
if (additionalData == null)
data = "null";
else
data = Base64.getEncoder().encodeToString(additionalData);
return "Vote (from:" + serviceName + " username:" + username
+ " address:" + address + " timeStamp:" + timeStamp
+ " additionalData:" + data + ")";
}
/**
* Sets the serviceName.
*
* @param serviceName The new serviceName
*/
@Deprecated
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
/**
* Gets the serviceName.
*
* @return The serviceName
*/
public String getServiceName() {
return serviceName;
}
/**
* Sets the username.
*
* @param username The new username
*/
@Deprecated
public void setUsername(String username) {
this.username = username;
}
/**
* Gets the username.
*
* @return The username
*/
public String getUsername() {
return username;
}
/**
* Sets the address.
*
* @param address The new address
*/
@Deprecated
public void setAddress(String address) {
this.address = address;
}
/**
* Gets the address.
*
* @return The address
*/
public String getAddress() {
return address;
}
/**
* Sets the time stamp.
*
* @param timeStamp The new time stamp
*/
@Deprecated
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
/**
* Gets the time stamp.
*
* @return The time stamp
*/
public String getTimeStamp() {
return timeStamp;
}
/**
* Returns additional data sent with the vote, if it exists.
*
* @return Additional data sent with the vote
*/
public byte[] getAdditionalData() {
return additionalData == null ? null : additionalData.clone();
}
public JsonObject serialize() {
JsonObject ret = new JsonObject();
ret.addProperty("serviceName", serviceName);
ret.addProperty("username", username);
ret.addProperty("address", address);
ret.addProperty("timestamp", timeStamp);
if (additionalData != null)
ret.addProperty("additionalData", Base64.getEncoder().encodeToString(additionalData));
return ret;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Vote)) return false;
Vote vote = (Vote) o;
if (!serviceName.equals(vote.serviceName)) return false;
if (!username.equals(vote.username)) return false;
if (!address.equals(vote.address)) return false;
if (!timeStamp.equals(vote.timeStamp)) return false;
return Arrays.equals(additionalData, vote.additionalData);
}
@Override
public int hashCode() {
int result = serviceName.hashCode();
result = 31 * result + username.hashCode();
result = 31 * result + address.hashCode();
result = 31 * result + timeStamp.hashCode();
return result;
}
}
@@ -1,77 +0,0 @@
package com.vexsoftware.votifier.model;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class VoteTest {
@Test
public void testVoteConstructFromJsonObject() {
String serviceName = "serviceNameA";
String username = "usernameA";
String address = "home";
String timestamp = "1234";
JsonObject o = new JsonObject();
o.addProperty("serviceName", serviceName);
o.addProperty("username", username);
o.addProperty("address", address);
o.addProperty("timestamp", timestamp);
Vote v = new Vote(o);
assertEquals(serviceName, v.getServiceName());
assertEquals(username, v.getUsername());
assertEquals(address, v.getAddress());
assertEquals(timestamp, v.getTimeStamp());
}
@Test
public void testVoteConstructFromJsonObjectLongTS() {
String serviceName = "serviceNameA";
String username = "usernameA";
String address = "home";
JsonObject o = new JsonObject();
o.addProperty("serviceName", serviceName);
o.addProperty("username", username);
o.addProperty("address", address);
o.addProperty("timestamp", 1234);
Vote v = new Vote(o);
assertEquals(serviceName, v.getServiceName());
assertEquals(username, v.getUsername());
assertEquals(address, v.getAddress());
assertEquals("1234", v.getTimeStamp());
}
@Test
public void testVoteSerialize() {
Vote v = new Vote("service", "username", "address", "1234");
JsonObject o = v.serialize();
assertEquals(4, o.size());
assertEquals("service", o.get("serviceName").getAsString());
assertEquals("username", o.get("username").getAsString());
assertEquals("address", o.get("address").getAsString());
assertEquals("1234", o.get("timestamp").getAsString());
}
@Test
public void testVoteEquals() {
Vote v1 = new Vote("service", "username", "address", "1234");
assertEquals(v1, v1);
assertEquals(v1.hashCode(), new Vote("service", "username", "address", "1234").hashCode());
assertEquals(v1, new Vote("service", "username", "address", "1234"));
assertNotEquals(v1, new Object());
assertNotEquals(v1, null);
assertNotEquals(v1, new Vote("service2", "username", "address", "1234"));
assertNotEquals(v1, new Vote("service", "username1", "address", "1234"));
assertNotEquals(v1, new Vote("service", "username", "address1", "1234"));
assertNotEquals(v1, new Vote("service", "username", "address", "12345"));
}
}
+5 -21
View File
@@ -1,19 +1,14 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id "com.github.spotbugs" version "4.7.1" apply false
id "net.kyori.blossom" version "1.3.0" apply false
}
allprojects {
group 'com.vexsoftware'
version '2.7.3'
group 'io.ibj.nuvotifier'
version '3.0.0-SNAPSHOT'
}
subprojects {
apply plugin: 'java'
apply plugin: "com.github.spotbugs"
apply plugin: 'java-library'
sourceCompatibility = 1.8
@@ -35,9 +30,8 @@ subprojects {
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.2"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.4.2"
testImplementation "org.junit.jupiter:junit-jupiter-params:5.4.2"
testImplementation "org.mockito:mockito-core:2.+"
testRuntime "org.junit.jupiter:junit-jupiter-engine:5.4.2"
testCompile "org.mockito:mockito-core:2.+"
}
processResources {
@@ -47,14 +41,4 @@ subprojects {
test {
useJUnitPlatform()
}
tasks.withType(com.github.spotbugs.snom.SpotBugsTask) {
reports {
html {
enabled = true
destination = file("$buildDir/reports/spotbugs/main/spotbugs.html")
stylesheet = 'fancy-hist.xsl'
}
}
}
}
+4
View File
@@ -0,0 +1,4 @@
dependencies {
api group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
implementation project(":model")
}
@@ -1,5 +1,6 @@
package com.vexsoftware.votifier.model;
import io.ibj.nuvotifier.model.NuVote;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
@@ -19,14 +20,14 @@ public class VotifierEvent extends Event {
/**
* Encapsulated vote record.
*/
private Vote vote;
private NuVote vote;
/**
* Constructs a vote event that encapsulated the given vote record.
*
* @param vote vote record
*/
public VotifierEvent(final Vote vote) {
public VotifierEvent(final NuVote vote) {
this.vote = vote;
}
-6
View File
@@ -1,6 +0,0 @@
dependencies {
compileOnly group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
implementation project(":nuvotifier-api")
implementation project(":nuvotifier-common")
}
@@ -1,57 +0,0 @@
package com.vexsoftware.votifier;
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import org.bukkit.scheduler.BukkitTask;
import java.util.concurrent.TimeUnit;
class BukkitScheduler implements VotifierScheduler {
private final NuVotifierBukkit plugin;
public BukkitScheduler(NuVotifierBukkit plugin) {
this.plugin = plugin;
}
private int toTicks(int time, TimeUnit unit) {
return (int) (unit.toMillis(time) / 50);
}
@Override
public ScheduledVotifierTask sync(Runnable runnable) {
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTask(plugin, runnable));
}
@Override
public ScheduledVotifierTask onPool(Runnable runnable) {
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable));
}
@Override
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskLater(plugin, runnable, toTicks(delay, unit)));
}
@Override
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, toTicks(delay, unit)));
}
@Override
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, toTicks(delay, unit), toTicks(repeat, unit)));
}
private static class BukkitTaskWrapper implements ScheduledVotifierTask {
private final BukkitTask task;
private BukkitTaskWrapper(BukkitTask task) {
this.task = task;
}
@Override
public void cancel() {
task.cancel();
}
}
}
@@ -1,374 +0,0 @@
/*
* Copyright (C) 2012 Vex Software LLC
* This file is part of Votifier.
*
* Votifier 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.
*
* Votifier 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 Votifier. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vexsoftware.votifier;
import com.vexsoftware.votifier.cmd.NVReloadCmd;
import com.vexsoftware.votifier.cmd.TestVoteCmd;
import com.vexsoftware.votifier.forwarding.BukkitPluginMessagingForwardingSink;
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSink;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.model.VotifierEvent;
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
import com.vexsoftware.votifier.platform.JavaUtilLogger;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import com.vexsoftware.votifier.util.IOUtil;
import com.vexsoftware.votifier.util.KeyCreator;
import com.vexsoftware.votifier.util.TokenUtil;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.Key;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
/**
* The main Votifier plugin class.
*
* @author Blake Beaupain
* @author Kramer Campbell
*/
public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, VotifierPlugin, ForwardedVoteListener {
/**
* The server bootstrap.
*/
private VotifierServerBootstrap bootstrap;
/**
* The RSA key pair.
*/
private KeyPair keyPair;
/**
* Debug mode flag
*/
private boolean debug;
/**
* Keys used for websites.
*/
private Map<String, Key> tokens = new HashMap<>();
private ForwardingVoteSink forwardingMethod;
private VotifierScheduler scheduler;
private LoggingAdapter pluginLogger;
private boolean loadAndBind() {
scheduler = new BukkitScheduler(this);
pluginLogger = new JavaUtilLogger(getLogger());
if (!getDataFolder().exists()) {
if (!getDataFolder().mkdir()) {
throw new RuntimeException("Unable to create the plugin data folder " + getDataFolder());
}
}
// Handle configuration.
File config = new File(getDataFolder(), "config.yml");
/*
* Use IP address from server.properties as a default for
* configurations. Do not use InetAddress.getLocalHost() as it most
* likely will return the main server address instead of the address
* assigned to the server.
*/
String hostAddr = Bukkit.getServer().getIp();
if (hostAddr == null || hostAddr.length() == 0)
hostAddr = "0.0.0.0";
/*
* Create configuration file if it does not exists; otherwise, load it
*/
if (!config.exists()) {
try {
// First time run - do some initialization.
getLogger().info("Configuring Votifier for the first time...");
// Initialize the configuration file.
if (!config.createNewFile()) {
throw new IOException("Unable to create the config file at " + config);
}
// Load and manually replace variables in the configuration.
String cfgStr = new String(IOUtil.readAllBytes(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
String token = TokenUtil.newToken();
cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
Files.copy(new ByteArrayInputStream(cfgStr.getBytes(StandardCharsets.UTF_8)), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
/*
* Remind hosted server admins to be sure they have the right
* port number.
*/
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Assigning NuVotifier to listen on port 8192. If you are hosting Craftbukkit on a");
getLogger().info("shared server please check with your hosting provider to verify that this port");
getLogger().info("is available for your use. Chances are that your hosting provider will assign");
getLogger().info("a different port, which you need to specify in config.yml");
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Your default NuVotifier token is " + token + ".");
getLogger().info("You will need to provide this token when you submit your server to a voting");
getLogger().info("list.");
getLogger().info("------------------------------------------------------------------------------");
} catch (Exception ex) {
getLogger().log(Level.SEVERE, "Error creating configuration file", ex);
return false;
}
}
YamlConfiguration cfg;
File rsaDirectory = new File(getDataFolder(), "rsa");
// Load configuration.
cfg = YamlConfiguration.loadConfiguration(config);
/*
* Create RSA directory and keys if it does not exist; otherwise, read
* keys.
*/
try {
if (!rsaDirectory.exists()) {
if (!rsaDirectory.mkdir()) {
throw new RuntimeException("Unable to create the RSA key folder " + rsaDirectory);
}
keyPair = RSAKeygen.generate(2048);
RSAIO.save(rsaDirectory, keyPair);
} else {
keyPair = RSAIO.load(rsaDirectory);
}
} catch (Exception ex) {
getLogger().log(Level.SEVERE,
"Error reading configuration file or RSA tokens", ex);
return false;
}
// the quiet flag always runs priority to the debug flag
if (cfg.isBoolean("quiet")) {
debug = !cfg.getBoolean("quiet");
} else {
// otherwise, default to being noisy
debug = cfg.getBoolean("debug", true);
}
// Load Votifier tokens.
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
if (tokenSection != null) {
Map<String, Object> websites = tokenSection.getValues(false);
for (Map.Entry<String, Object> website : websites.entrySet()) {
tokens.put(website.getKey(), KeyCreator.createKeyFrom(website.getValue().toString()));
getLogger().info("Loaded token for website: " + website.getKey());
}
} else {
String token = TokenUtil.newToken();
tokenSection = cfg.createSection("tokens");
tokenSection.set("default", token);
tokens.put("default", KeyCreator.createKeyFrom(token));
try {
cfg.save(config);
} catch (IOException e) {
getLogger().log(Level.SEVERE,
"Error generating Votifier token", e);
return false;
}
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
getLogger().info("Your default Votifier token is " + token + ".");
getLogger().info("You will need to provide this token when you submit your server to a voting");
getLogger().info("list.");
getLogger().info("------------------------------------------------------------------------------");
}
// Initialize the receiver.
final String host = cfg.getString("host", hostAddr);
final int port = cfg.getInt("port", 8192);
if (!debug)
getLogger().info("QUIET mode enabled!");
if (port >= 0) {
final boolean disablev1 = cfg.getBoolean("disable-v1-protocol");
if (disablev1) {
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
getLogger().info("currently support the modern Votifier protocol in NuVotifier.");
getLogger().info("------------------------------------------------------------------------------");
}
this.bootstrap = new VotifierServerBootstrap(host, port, this, disablev1);
this.bootstrap.start(error -> {});
} else {
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Your Votifier port is less than 0, so we assume you do NOT want to start the");
getLogger().info("votifier port server! Votifier will not listen for votes over any port, and");
getLogger().info("will only listen for pluginMessaging forwarded votes!");
getLogger().info("------------------------------------------------------------------------------");
}
ConfigurationSection forwardingConfig = cfg.getConfigurationSection("forwarding");
if (forwardingConfig != null) {
String method = forwardingConfig.getString("method", "none").toLowerCase(); //Default to lower case for case-insensitive searches
if ("none".equals(method)) {
getLogger().info("Method none selected for vote forwarding: Votes will not be received from a forwarder.");
} else if ("pluginmessaging".equals(method)) {
String channel = forwardingConfig.getString("pluginMessaging.channel", "NuVotifier");
try {
forwardingMethod = new BukkitPluginMessagingForwardingSink(this, channel, this);
getLogger().info("Receiving votes over PluginMessaging channel '" + channel + "'.");
} catch (RuntimeException e) {
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
}
} else {
getLogger().severe("No vote forwarding method '" + method + "' known. Defaulting to noop implementation.");
}
}
return true;
}
private void halt() {
// Shut down the network handlers.
if (bootstrap != null) {
bootstrap.shutdown();
bootstrap = null;
}
if (forwardingMethod != null) {
forwardingMethod.halt();
forwardingMethod = null;
}
}
@Override
public void onEnable() {
getCommand("nvreload").setExecutor(new NVReloadCmd(this));
getCommand("testvote").setExecutor(new TestVoteCmd(this));
if (!loadAndBind()) {
gracefulExit();
setEnabled(false); // safer to just bomb out
}
}
@Override
public void onDisable() {
halt();
getLogger().info("Votifier disabled.");
}
public boolean reload() {
try {
halt();
} catch (Exception ex) {
getLogger().log(Level.SEVERE, "On halt, an exception was thrown. This may be fine!", ex);
}
if (loadAndBind()) {
getLogger().info("Reload was successful.");
return true;
} else {
try {
halt();
getLogger().log(Level.SEVERE, "On reload, there was a problem with the configuration. Votifier currently does nothing!");
} catch (Exception ex) {
getLogger().log(Level.SEVERE, "On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!", ex);
}
return false;
}
}
private void gracefulExit() {
getLogger().log(Level.SEVERE, "Votifier did not initialize properly!");
}
@Override
public LoggingAdapter getPluginLogger() {
return pluginLogger;
}
@Override
public VotifierScheduler getScheduler() {
return scheduler;
}
public boolean isDebug() {
return debug;
}
@Override
public Map<String, Key> getTokens() {
return tokens;
}
@Override
public KeyPair getProtocolV1Key() {
return keyPair;
}
@Override
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) {
if (debug) {
getLogger().info("Got a " + protocolVersion.humanReadable + " vote record from " + remoteAddress + " -> " + vote);
}
Bukkit.getScheduler().runTask(this, () -> fireVotifierEvent(vote));
}
@Override
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
if (debug) {
if (alreadyHandledVote) {
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
"occurred with a vote from " + remoteAddress, throwable);
} else {
getLogger().log(Level.SEVERE, "Unable to process vote from " + remoteAddress, throwable);
}
} else if (!alreadyHandledVote) {
getLogger().log(Level.SEVERE, "Unable to process vote from " + remoteAddress);
}
}
@Override
public void onForward(final Vote v) {
if (debug) {
getLogger().info("Got a forwarded vote -> " + v);
}
Bukkit.getScheduler().runTask(this, () -> fireVotifierEvent(v));
}
private void fireVotifierEvent(Vote vote) {
if (VotifierEvent.getHandlerList().getRegisteredListeners().length == 0) {
getLogger().log(Level.SEVERE, "A vote was received, but you don't have any listeners available to listen for it.");
getLogger().log(Level.SEVERE, "See https://github.com/NuVotifier/NuVotifier/wiki/Setup-Guide#vote-listeners for");
getLogger().log(Level.SEVERE, "a list of listeners you can configure.");
}
Bukkit.getPluginManager().callEvent(new VotifierEvent(vote));
}
}
@@ -1,31 +0,0 @@
package com.vexsoftware.votifier.cmd;
import com.vexsoftware.votifier.NuVotifierBukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class NVReloadCmd implements CommandExecutor {
private final NuVotifierBukkit plugin;
public NVReloadCmd(NuVotifierBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender.hasPermission("nuvotifier.reload")) {
sender.sendMessage(ChatColor.GRAY + "Reloading NuVotifier...");
if (plugin.reload()) {
sender.sendMessage(ChatColor.DARK_GREEN + "NuVotifier has been reloaded!");
} else {
sender.sendMessage(ChatColor.DARK_RED + "Looks like there was a problem reloading NuVotifier, check the console!");
}
} else {
sender.sendMessage(ChatColor.DARK_RED + "You do not have permission to do this!");
}
return true;
}
}
@@ -1,40 +0,0 @@
package com.vexsoftware.votifier.cmd;
import com.vexsoftware.votifier.NuVotifierBukkit;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.ArgsToVote;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class TestVoteCmd implements CommandExecutor {
private final NuVotifierBukkit plugin;
public TestVoteCmd(NuVotifierBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender.hasPermission("nuvotifier.testvote")) {
Vote v;
try {
v = ArgsToVote.parse(args);
} catch (IllegalArgumentException e) {
sender.sendMessage(ChatColor.DARK_RED + "Error while parsing arguments to create test vote: " + e.getMessage());
sender.sendMessage(ChatColor.GRAY + "Usage hint: /testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]");
return true;
}
plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
sender.sendMessage(ChatColor.GREEN + "Test vote executed: " + v.toString());
} else {
sender.sendMessage(ChatColor.DARK_RED + "You do not have permission to do this!");
}
return true;
}
}
@@ -1,43 +0,0 @@
package com.vexsoftware.votifier.forwarding;
import com.vexsoftware.votifier.NuVotifierBukkit;
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSink;
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import java.util.logging.Level;
/**
* Created by Joe Hirschfeld on 10/20/2015.
*/
public class BukkitPluginMessagingForwardingSink extends AbstractPluginMessagingForwardingSink implements PluginMessageListener {
public BukkitPluginMessagingForwardingSink(Plugin p, String channel, ForwardedVoteListener listener) {
super(listener);
Validate.notNull(channel, "Channel cannot be null.");
this.channel = channel;
Bukkit.getMessenger().registerIncomingPluginChannel(p, channel, this);
this.p = p;
}
private final Plugin p;
private final String channel;
@Override
public void halt() {
Bukkit.getMessenger().unregisterIncomingPluginChannel(p, channel, this);
}
@Override
public void onPluginMessageReceived(String s, Player player, byte[] bytes) {
try {
this.handlePluginMessage(bytes);
} catch (Exception e) {
p.getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
}
}
}
@@ -1,25 +0,0 @@
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
host: %ip%
# Port to listen for new votes on
port: 8192
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are
# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.
disable-v1-protocol: false
# All tokens, labeled by the serviceName of each server list.
tokens:
# Default token for all server lists, if another isn't supplied.
default: %default_token%
# Configuration section for all vote forwarding to NuVotifier
forwarding:
# Sets whether to set up a remote method for fowarding. Supported methods:
# - none - Does not set up a forwarding method.
# - pluginMessaging - Sets up plugin messaging
method: none
pluginMessaging:
channel: nuvotifier:votes
-26
View File
@@ -1,26 +0,0 @@
name: Votifier
main: com.vexsoftware.votifier.NuVotifierBukkit
version: @app.version@
description: A plugin that gets notified when votes are made for the server on toplists.
authors: [Ichbinjoe, blakeman8192, Kramer, tuxed]
api-version: 1.13
commands:
nvreload:
description: Reloads the NuVotifier configuration
permission: nuvotifier.reload
permission-message: "You do not have permission to run this command."
usage: "/nvreload"
testvote:
description: Sends a test vote to the server
permission: nuvotifier.testvote
permission-message: "You do not have permission to run this command."
usage: "/testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]"
permissions:
nuvotifier.reload:
description: Allows you to reload the NuVotifier plugin
default: op
nuvotifier.testvote:
description: Allows you to send a test vote
default: op
+4
View File
@@ -0,0 +1,4 @@
dependencies {
api group: "net.md-5", name: "bungeecord-api", version: "1.8-SNAPSHOT"
implementation project(":model")
}
@@ -1,6 +1,6 @@
package com.vexsoftware.votifier.bungee.events;
import com.vexsoftware.votifier.model.Vote;
import io.ibj.nuvotifier.model.NuVote;
import net.md_5.bungee.api.plugin.Event;
/**
@@ -11,13 +11,13 @@ public class VotifierEvent extends Event {
/**
* The vote.
**/
private final Vote vote;
private final NuVote vote;
public VotifierEvent(Vote vote) {
public VotifierEvent(NuVote vote) {
this.vote = vote;
}
public Vote getVote() {
public NuVote getVote() {
return vote;
}
}
-5
View File
@@ -1,5 +0,0 @@
dependencies {
compileOnly group: "net.md-5", name: "bungeecord-api", version: "1.8-SNAPSHOT"
implementation project(":nuvotifier-api")
implementation project(":nuvotifier-common")
}
@@ -1,29 +0,0 @@
package com.vexsoftware.votifier.bungee;
import com.vexsoftware.votifier.platform.BackendServer;
import net.md_5.bungee.api.config.ServerInfo;
import java.util.Objects;
public class BungeeBackendServer implements BackendServer {
private final ServerInfo info;
public BungeeBackendServer(ServerInfo info) {
this.info = Objects.requireNonNull(info, "info");
}
@Override
public String getName() {
return info.getName();
}
@Override
public boolean sendPluginMessage(String channel, byte[] data) {
return info.sendData(channel, data, false);
}
@Override
public String toString() {
return info.getName();
}
}
@@ -1,53 +0,0 @@
package com.vexsoftware.votifier.bungee;
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import net.md_5.bungee.api.scheduler.ScheduledTask;
import java.util.concurrent.TimeUnit;
class BungeeScheduler implements VotifierScheduler {
private final NuVotifier plugin;
public BungeeScheduler(NuVotifier plugin) {
this.plugin = plugin;
}
@Override
public ScheduledVotifierTask sync(Runnable runnable) {
return onPool(runnable);
}
@Override
public ScheduledVotifierTask onPool(Runnable runnable) {
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().runAsync(plugin, runnable));
}
@Override
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
return delayedOnPool(runnable, delay, unit);
}
@Override
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().schedule(plugin, runnable, delay, unit));
}
@Override
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().schedule(plugin, runnable, delay, repeat, unit));
}
private static class BungeeTaskWrapper implements ScheduledVotifierTask {
private final ScheduledTask task;
private BungeeTaskWrapper(ScheduledTask task) {
this.task = task;
}
@Override
public void cancel() {
this.task.cancel();
}
}
}
@@ -1,424 +0,0 @@
package com.vexsoftware.votifier.bungee;
import com.google.common.collect.ImmutableList;
import com.vexsoftware.votifier.VoteHandler;
import com.vexsoftware.votifier.bungee.cmd.NVReloadCmd;
import com.vexsoftware.votifier.bungee.cmd.TestVoteCmd;
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
import com.vexsoftware.votifier.platform.BackendServer;
import com.vexsoftware.votifier.platform.JavaUtilLogger;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
import com.vexsoftware.votifier.bungee.events.VotifierEvent;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
import com.vexsoftware.votifier.support.forwarding.cache.MemoryVoteCache;
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
import com.vexsoftware.votifier.util.IOUtil;
import com.vexsoftware.votifier.util.KeyCreator;
import com.vexsoftware.votifier.util.TokenUtil;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.Key;
import java.security.KeyPair;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.stream.Collectors;
public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlugin {
/**
* The server channel.
*/
private VotifierServerBootstrap bootstrap;
/**
* The RSA key pair.
*/
private KeyPair keyPair;
/**
* Debug mode flag
*/
private boolean debug;
/**
* Keys used for websites.
*/
private Map<String, Key> tokens = new HashMap<>();
/**
* Method used to forward votes to downstream servers
*/
private ForwardingVoteSource forwardingMethod;
private VotifierScheduler scheduler;
private LoggingAdapter pluginLogger;
private void loadAndBind() {
if (!getDataFolder().exists()) {
if (!getDataFolder().mkdir()) {
throw new RuntimeException("Unable to create the plugin data folder " + getDataFolder());
}
}
// Handle configuration.
File config = new File(getDataFolder() , "config.yml");
File rsaDirectory = new File(getDataFolder() , "rsa");
Configuration configuration;
if (!config.exists()) {
try {
// First time run - do some initialization.
getLogger().info("Configuring Votifier for the first time...");
// Initialize the configuration file.
if (!config.createNewFile()) {
throw new IOException("Unable to create the config file at " + config);
}
String cfgStr = new String(IOUtil.readAllBytes(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
String token = TokenUtil.newToken();
cfgStr = cfgStr.replace("%default_token%", token);
Files.copy(new ByteArrayInputStream(cfgStr.getBytes(StandardCharsets.UTF_8)), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
/*
* Remind hosted server admins to be sure they have the right
* port number.
*/
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Assigning NuVotifier to listen on port 8192. If you are hosting BungeeCord on a");
getLogger().info("shared server please check with your hosting provider to verify that this port");
getLogger().info("is available for your use. Chances are that your hosting provider will assign");
getLogger().info("a different port, which you need to specify in config.yml");
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Assigning NuVotifier to listen to interface 0.0.0.0. This is usually alright,");
getLogger().info("however, if you want NuVotifier to only listen to one interface for security ");
getLogger().info("reasons (or you use a shared host), you may change this in the config.yml.");
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Your default Votifier token is " + token + ".");
getLogger().info("You will need to provide this token when you submit your server to a voting");
getLogger().info("list.");
getLogger().info("------------------------------------------------------------------------------");
} catch (Exception ex) {
throw new RuntimeException("Unable to create configuration file", ex);
}
}
// Load the configuration.
try {
configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(config);
} catch (IOException e) {
throw new RuntimeException("Unable to load configuration", e);
}
/*
* Create RSA directory and keys if it does not exist; otherwise, read
* keys.
*/
try {
if (!rsaDirectory.exists()) {
if (!rsaDirectory.mkdir()) {
throw new RuntimeException("Unable to create the RSA key folder " + rsaDirectory);
}
keyPair = RSAKeygen.generate(2048);
RSAIO.save(rsaDirectory, keyPair);
} else {
keyPair = RSAIO.load(rsaDirectory);
}
} catch (Exception ex) {
throw new RuntimeException("Error reading RSA tokens", ex);
}
// Load Votifier tokens.
Configuration tokenSection = configuration.getSection("tokens");
if (configuration.get("tokens") != null) {
for (String s : tokenSection.getKeys()) {
tokens.put(s, KeyCreator.createKeyFrom(tokenSection.getString(s)));
getLogger().info("Loaded token for website: " + s);
}
} else {
String token = TokenUtil.newToken();
configuration.set("tokens", Collections.singletonMap("default", token));
tokens.put("default", KeyCreator.createKeyFrom(token));
try {
ConfigurationProvider.getProvider(YamlConfiguration.class).save(configuration, config);
} catch (IOException e) {
throw new RuntimeException("Error generating Votifier token", e);
}
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
getLogger().info("Your default Votifier token is " + token + ".");
getLogger().info("You will need to provide this token when you submit your server to a voting");
getLogger().info("list.");
getLogger().info("------------------------------------------------------------------------------");
}
// Initialize the receiver.
final String host = configuration.getString("host", "0.0.0.0");
final int port = configuration.getInt("port", 8192);
if (configuration.get("quiet") != null)
debug = !configuration.getBoolean("quiet");
else
debug = configuration.getBoolean("debug", true);
if (!debug)
getLogger().info("QUIET mode enabled!");
final boolean disablev1 = configuration.getBoolean("disable-v1-protocol");
if (disablev1) {
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
getLogger().info("currently support the modern Votifier protocol in NuVotifier.");
getLogger().info("------------------------------------------------------------------------------");
}
// Must set up server asynchronously due to BungeeCord goofiness.
FutureTask<?> initTask = new FutureTask<>(Executors.callable(() -> {
this.bootstrap = new VotifierServerBootstrap(host, port, NuVotifier.this, disablev1);
this.bootstrap.start(err -> {});
}));
getProxy().getScheduler().runAsync(this, initTask);
try {
initTask.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Unable to start server", e);
}
Configuration fwdCfg = configuration.getSection("forwarding");
String fwdMethod = fwdCfg.getString("method", "none").toLowerCase();
if ("none".equals(fwdMethod)) {
getLogger().info("Method none selected for vote forwarding: Votes will not be forwarded to backend servers.");
} else if ("pluginmessaging".equals(fwdMethod)) {
String channel = fwdCfg.getString("pluginMessaging.channel", "NuVotifier");
String cacheMethod = fwdCfg.getString("pluginMessaging.cache", "file").toLowerCase();
VoteCache voteCache = null;
if ("none".equals(cacheMethod)) {
getLogger().info("Vote cache none selected for caching: votes that cannot be immediately delivered will be lost.");
} else if ("memory".equals(cacheMethod)) {
voteCache = new MemoryVoteCache(
this,
fwdCfg.getInt("pluginMessaging.memory.cacheTime", -1));
getLogger().info("Using in-memory cache for votes that are not able to be delivered.");
} else if ("file".equals(cacheMethod)) {
try {
voteCache = new FileVoteCache(
this,
new File(getDataFolder(), fwdCfg.getString("pluginMessaging.file.name")),
fwdCfg.getInt("pluginMessaging.file.cacheTime", -1));
} catch (IOException e) {
getLogger().log(Level.SEVERE, "Unload to load file cache. Votes will be lost!", e);
}
}
int dumpRate = fwdCfg.getInt("pluginMessaging.dumpRate", 5);
ServerFilter filter = new ServerFilter(
fwdCfg.getStringList("pluginMessaging.excludedServers"),
fwdCfg.getBoolean("pluginMessaging.whitelist", false)
);
if (!fwdCfg.getBoolean("pluginMessaging.onlySendToJoinedServer")) {
try {
forwardingMethod = new PluginMessagingForwardingSource(channel, filter, this, voteCache, dumpRate);
getLogger().info("Forwarding votes over PluginMessaging channel '" + channel + "' for vote forwarding!");
} catch (RuntimeException e) {
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
}
} else {
try {
String fallbackServer = fwdCfg.getString("pluginMessaging.joinedServerFallback", null);
if (fallbackServer != null && fallbackServer.isEmpty()) fallbackServer = null;
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, filter, voteCache, fallbackServer, dumpRate);
getLogger().info("Forwarding votes over PluginMessaging channel '" + channel + "' for vote forwarding for online players!");
} catch (RuntimeException e) {
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
}
}
} else if ("proxy".equals(fwdMethod)) {
Configuration serverSection = fwdCfg.getSection("proxy");
List<ProxyForwardingVoteSource.BackendServer> serverList = new ArrayList<>();
for (String s : serverSection.getKeys()) {
Configuration section = serverSection.getSection(s);
InetAddress address;
try {
address = InetAddress.getByName(section.getString("address"));
} catch (UnknownHostException e) {
getLogger().info("Address " + section.getString("address") + " couldn't be looked up. Ignoring!");
continue;
}
Key token = null;
try {
token = KeyCreator.createKeyFrom(section.getString("token", section.getString("key")));
} catch (IllegalArgumentException e) {
getLogger().log(Level.SEVERE,
"An exception occurred while attempting to add proxy target '" + s + "' - maybe your token is wrong? " +
"Votes will not be forwarded to this server!", e);
}
if (token != null) {
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
new InetSocketAddress(address, section.getInt("port")),
token);
serverList.add(server);
}
}
forwardingMethod = bootstrap.createForwardingSource(serverList, null);
getLogger().info("Forwarding votes from this NuVotifier instance to another NuVotifier server.");
} else {
getLogger().severe("No vote forwarding method '" + fwdMethod + "' known. Defaulting to noop implementation.");
}
}
@Override
public void onEnable() {
scheduler = new BungeeScheduler(this);
pluginLogger = new JavaUtilLogger(getLogger());
PluginManager pm = ProxyServer.getInstance().getPluginManager();
pm.registerCommand(this, new NVReloadCmd(this));
pm.registerCommand(this, new TestVoteCmd(this));
pm.registerListener(this, new ReloadListener(this));
loadAndBind();
}
private void halt() {
// Shut down the network handlers.
if (bootstrap != null) {
bootstrap.shutdown();
bootstrap = null;
}
if (forwardingMethod != null) {
forwardingMethod.halt();
forwardingMethod = null;
}
}
public boolean reload() {
try {
halt();
} catch (Exception ex) {
getLogger().log(Level.SEVERE, "On halt, an exception was thrown. This may be fine!", ex);
}
try {
loadAndBind();
getLogger().info("Reload was successful.");
return true;
} catch (Exception ex) {
try {
halt();
getLogger().log(Level.SEVERE, "On reload, there was a problem with the configuration. Votifier currently does nothing!", ex);
} catch (Exception ex2) {
getLogger().log(Level.SEVERE, "On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!", ex);
getLogger().log(Level.SEVERE, "(halt exception)", ex2);
}
return false;
}
}
@Override
public void onDisable() {
halt();
getLogger().info("Votifier disabled.");
}
@Override
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) {
if (debug) {
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
getLogger().info("Got a protocol v1 vote record from " + remoteAddress + " -> " + vote);
} else {
getLogger().info("Got a protocol v2 vote record from " + remoteAddress + " -> " + vote);
}
}
getProxy().getScheduler().runAsync(this, () -> getProxy().getPluginManager().callEvent(new VotifierEvent(vote)));
if (forwardingMethod != null) {
getProxy().getScheduler().runAsync(this, () -> forwardingMethod.forward(vote));
}
}
@Override
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
if (debug) {
if (alreadyHandledVote) {
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
"occurred with a vote from " + remoteAddress, throwable);
} else {
getLogger().log(Level.SEVERE, "Unable to process vote from " + remoteAddress, throwable);
}
} else if (!alreadyHandledVote) {
getLogger().log(Level.SEVERE, "Unable to process vote from " + remoteAddress);
}
}
@Override
public Map<String, Key> getTokens() {
return tokens;
}
@Override
public KeyPair getProtocolV1Key() {
return keyPair;
}
@Override
public LoggingAdapter getPluginLogger() {
return pluginLogger;
}
@Override
public VotifierScheduler getScheduler() {
return scheduler;
}
public boolean isDebug() {
return debug;
}
@Override
public Collection<BackendServer> getAllBackendServers() {
return getProxy().getServers().values().stream()
.map(BungeeBackendServer::new)
.collect(Collectors.toList());
}
@Override
public Optional<BackendServer> getServer(String name) {
ServerInfo info = getProxy().getServerInfo(name);
return Optional.ofNullable(info).map(BungeeBackendServer::new);
}
}
@@ -1,63 +0,0 @@
package com.vexsoftware.votifier.bungee;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.platform.BackendServer;
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
/**
* @author Joseph Hirschfeld
* @date 12/31/2015
*/
public final class OnlineForwardPluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
public OnlineForwardPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, ServerFilter serverFilter,
VoteCache cache, String fallbackServer, int dumpRate) {
super(channel, serverFilter, nuVotifier, cache, dumpRate);
this.fallbackServer = fallbackServer;
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
}
private final String fallbackServer;
@Override
public void forward(Vote v) {
ProxiedPlayer p = ProxyServer.getInstance().getPlayer(v.getUsername());
if (p != null && p.getServer() != null &&
serverFilter.isAllowed(p.getServer().getInfo().getName())) {
if (forwardSpecific(new BungeeBackendServer(p.getServer().getInfo()), v)) {
if (plugin.isDebug()) {
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to server " + p.getServer().getInfo().getName());
}
return;
}
}
ServerInfo serverInfo = ProxyServer.getInstance().getServers().get(fallbackServer);
// nowhere to fall back to, yet still not online. lets save this vote yet!
if (serverInfo == null)
attemptToAddToPlayerCache(v, v.getUsername());
else if (!forwardSpecific(new BungeeBackendServer(serverInfo), v))
attemptToAddToCache(v, fallbackServer);
}
@EventHandler
public void onPluginMessage(PluginMessageEvent e) {
if (e.getTag().equals(channel)) e.setCancelled(true);
}
@EventHandler
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
BackendServer server = new BungeeBackendServer(e.getServer().getInfo());
handlePlayerSwitch(server, e.getPlayer().getName());
onServerConnect(server);
}
}
@@ -1,33 +0,0 @@
package com.vexsoftware.votifier.bungee;
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class PluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
public PluginMessagingForwardingSource(String channel, ServerFilter serverFilter, NuVotifier nuVotifier, VoteCache cache, int dumpRate) {
super(channel, serverFilter, nuVotifier, cache, dumpRate);
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
}
protected PluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache, int dumpRate) {
super(channel, nuVotifier, voteCache, dumpRate);
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
}
@EventHandler
public void onPluginMessage(PluginMessageEvent e) {
if (e.getTag().equals(channel)) e.setCancelled(true);
}
@EventHandler
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
onServerConnect(new BungeeBackendServer(e.getServer().getInfo()));
}
}
@@ -1,18 +0,0 @@
package com.vexsoftware.votifier.bungee;
import net.md_5.bungee.api.event.ProxyReloadEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class ReloadListener implements Listener {
private final NuVotifier nuVotifier;
public ReloadListener(NuVotifier nuVotifier) {
this.nuVotifier = nuVotifier;
}
@EventHandler
public void onProxyReload(ProxyReloadEvent event) {
this.nuVotifier.reload();
}
}
@@ -1,44 +0,0 @@
package com.vexsoftware.votifier.bungee.cmd;
import com.vexsoftware.votifier.bungee.NuVotifier;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Command;
public class NVReloadCmd extends Command {
private final NuVotifier plugin;
private static final BaseComponent reloadingNuVotifier = new TextComponent("Reloading NuVotifier...");
private static final BaseComponent nuvotifierHasBeenReloaded = new TextComponent("NuVotifier has been reloaded!");
private static final BaseComponent problem = new TextComponent("Looks like there was a problem reloading NuVotifier, check the console!");
private static final BaseComponent permission = new TextComponent("You do not have permission to do this!");
static {
reloadingNuVotifier.setColor(ChatColor.GRAY);
nuvotifierHasBeenReloaded.setColor(ChatColor.DARK_GREEN);
problem.setColor(ChatColor.DARK_RED);
permission.setColor(ChatColor.DARK_RED);
}
public NVReloadCmd(NuVotifier plugin) {
super("pnvreload", "nuvotifier.reload");
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (sender.hasPermission("nuvotifier.reload")) {
sender.sendMessage(reloadingNuVotifier);
if (plugin.reload()) {
sender.sendMessage(nuvotifierHasBeenReloaded);
} else {
sender.sendMessage(problem);
}
} else {
sender.sendMessage(permission);
}
}
}
@@ -1,51 +0,0 @@
package com.vexsoftware.votifier.bungee.cmd;
import com.vexsoftware.votifier.bungee.NuVotifier;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.ArgsToVote;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Command;
public class TestVoteCmd extends Command {
private final NuVotifier plugin;
private static final BaseComponent permission = new TextComponent("You do not have permission to do this!");
private static final BaseComponent usage = new TextComponent("Usage hint: /ptestvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]");
static {
usage.setColor(ChatColor.GRAY);
}
public TestVoteCmd(NuVotifier plugin) {
super("ptestvote", "nuvotifier.testvote");
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (sender.hasPermission("nuvotifier.testvote")) {
Vote v;
try {
v = ArgsToVote.parse(args);
} catch (IllegalArgumentException e) {
TextComponent c = new TextComponent("Error while parsing arguments to create test vote: " + e.getMessage());
c.setColor(ChatColor.DARK_RED);
sender.sendMessage(c);
sender.sendMessage(usage);
return;
}
plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
TextComponent c = new TextComponent("Test vote executed: " + v.toString());
c.setColor(ChatColor.GREEN);
sender.sendMessage(c);
} else {
sender.sendMessage(permission);
}
}
}
-4
View File
@@ -1,4 +0,0 @@
name: NuVotifier
main: com.vexsoftware.votifier.bungee.NuVotifier
version: @app.version@
author: Ichbinjoe, blakeman8192, Kramer, tuxed
@@ -1,70 +0,0 @@
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
host: 0.0.0.0
# Port to listen for new votes on
port: 8192
# All tokens, labeled by the serviceName of each server list.
tokens:
# Default token for all server lists, if another isn't supplied.
default: %default_token%
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
# option is currently not recommended as most voting sites only support the old protocol at present.
disable-v1-protocol: false
# Configuration section for all vote forwarding to NuVotifier
forwarding:
# Sets whether to set up a remote method for fowarding. Supported methods:
# - none - Does not set up a forwarding method.
# - pluginMessaging - Sets up plugin messaging.
# - proxy - Proxies votes to other NuVotifier servers from this server.
method: none
pluginMessaging:
channel: nuvotifier:votes
#Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
#to them. If excludedServers is not present, votes will be sent to all servers.
# If onlySendToJoinedServer is true and a player is on one of these servers, NuVotifier will process the vote as if the
# player was not on the network at all.
#excludedServers:
#- server1
#- server2
# If uncommented, turns excludedServers into an includedServers, where only servers on the list will be used.
#whitelist: true
# Sets which cache to use if there are no players on the server that the vote is being sent to. Supported caches:
# - none - Votes will simply be lost when no players are present on the server.
# - memory - Votes will be cached in memory when no players are present. When Bungee is restarted, the votes will be lost.
# - file - Votes will be saved to a local file every 3 minutes and on shutdown and reloaded when Bungee is restarted. This is suitable for
# most set ups.
cache: file
# Only sends the vote to the server in which the player is currently joined.
onlySendToJoinedServer: false
#Sets which server to send the vote in the event the player is not online when the vote is cast
#If you do not want the vote forwarded to a fallback, set this value to empty ('')
#ONLY USED IF onlySendToJoinedServer is true!!
joinedServerFallback: 'Hub'
# Defines how quickly to dump votes over a player's connection when offloading a cache in votes per second
dumpRate: 5
# Options for file caching.
file:
name: cached-votes.json
# days before a vote is considered 'dead' - removed from cache with a console warning
cacheTime: 10
# Options for memory caching.
memory:
# days before a vote is considered 'dead' and removed from memory. All votes are removed when the server restarts. -1 signifies no TTL
cacheTime: -1
# Specify servers to proxy votes for.
proxy:
Hub:
address: 127.0.0.1
port: 8193
token: MyKeyHere
Hub2:
address: 127.0.0.1
port: 8194
token: AnotherKeyHere
-8
View File
@@ -1,8 +0,0 @@
dependencies {
implementation project(":nuvotifier-api")
implementation group: "io.netty", name: "netty-handler", version: "4.1.49.Final"
implementation group: "io.netty", name: "netty-transport-native-epoll", version: "4.1.49.Final", classifier: "linux-x86_64"
implementation 'com.google.code.gson:gson:2.8.0'
testImplementation group: "org.json", name: "json", version: "20180130" // retain this for testing reasons
testImplementation 'com.google.guava:guava:28.1-jre'
}
@@ -1,15 +0,0 @@
package com.vexsoftware.votifier;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import io.netty.channel.Channel;
public interface VoteHandler {
void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception;
default void onError(Throwable throwable, boolean voteAlreadyCompleted, String remoteAddress) {
throw new RuntimeException("Unimplemented onError handler");
}
}
@@ -1,134 +0,0 @@
package com.vexsoftware.votifier.net;
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.FastThreadLocalThread;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class VotifierServerBootstrap {
private static final boolean USE_EPOLL = Epoll.isAvailable();
private final String host;
private final int port;
private final EventLoopGroup bossLoopGroup;
private final EventLoopGroup eventLoopGroup;
private final VotifierPlugin plugin;
private final boolean v1Disable;
private Channel serverChannel;
public VotifierServerBootstrap(String host, int port, VotifierPlugin plugin, boolean v1Disable) {
this.host = host;
this.port = port;
this.plugin = plugin;
this.v1Disable = v1Disable;
if (USE_EPOLL) {
this.bossLoopGroup = new EpollEventLoopGroup(1, createThreadFactory("Votifier epoll boss"));
this.eventLoopGroup = new EpollEventLoopGroup(3, createThreadFactory("Votifier epoll worker"));
plugin.getPluginLogger().info("Using epoll transport to accept votes.");
} else {
this.bossLoopGroup = new NioEventLoopGroup(1, createThreadFactory("Votifier NIO boss"));
this.eventLoopGroup = new NioEventLoopGroup(3, createThreadFactory("Votifier NIO worker"));
plugin.getPluginLogger().info("Using NIO transport to accept votes.");
}
}
private static ThreadFactory createThreadFactory(String name) {
return runnable -> {
FastThreadLocalThread thread = new FastThreadLocalThread(runnable, name);
thread.setDaemon(true);
return thread;
};
}
public void start(Consumer<Throwable> error) {
Objects.requireNonNull(error, "error");
VoteInboundHandler voteInboundHandler = new VoteInboundHandler(plugin);
new ServerBootstrap()
.channel(USE_EPOLL ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
.group(bossLoopGroup, eventLoopGroup)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) {
channel.attr(VotifierSession.KEY).set(new VotifierSession());
channel.attr(VotifierPlugin.KEY).set(plugin);
channel.pipeline().addLast("greetingHandler", VotifierGreetingHandler.INSTANCE);
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !v1Disable));
channel.pipeline().addLast("voteHandler", voteInboundHandler);
}
})
.bind(host, port)
.addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
serverChannel = future.channel();
plugin.getPluginLogger().info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
error.accept(null);
} else {
SocketAddress socketAddress = future.channel().localAddress();
if (socketAddress == null) {
socketAddress = new InetSocketAddress(host, port);
}
plugin.getPluginLogger().error("Votifier was not able to bind to " + socketAddress.toString(), future.cause());
error.accept(future.cause());
}
});
}
private Bootstrap client() {
return new Bootstrap()
.channel(USE_EPOLL ? EpollSocketChannel.class : NioSocketChannel.class)
.group(eventLoopGroup);
}
public ProxyForwardingVoteSource createForwardingSource(List<ProxyForwardingVoteSource.BackendServer> backendServers,
VoteCache voteCache) {
return new ProxyForwardingVoteSource(plugin, this::client, backendServers, voteCache);
}
public void shutdown() {
if (serverChannel != null) {
try {
serverChannel.close().syncUninterruptibly();
} catch (Exception e) {
plugin.getPluginLogger().error("Unable to shutdown server channel", e);
}
}
eventLoopGroup.shutdownGracefully();
bossLoopGroup.shutdownGracefully();
try {
bossLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
eventLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@@ -1,72 +0,0 @@
package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
import com.vexsoftware.votifier.util.QuietException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* Decodes original protocol votes.
*/
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
if (!ctx.channel().isActive()) {
buf.skipBytes(buf.readableBytes());
return;
}
if (buf.readableBytes() < 256) {
// The client might have not sent all the data yet, so don't eject the connection.
return;
}
if (buf.readableBytes() > 256) {
// They sent too much data.
throw new QuietException("Could not decrypt data from " + ctx.channel().remoteAddress() + " as it is too long. Attack?");
}
byte[] block = ByteBufUtil.getBytes(buf);
buf.skipBytes(buf.readableBytes());
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
try {
block = RSA.decrypt(block, plugin.getProtocolV1Key().getPrivate());
} catch (Exception e) {
if (plugin.isDebug()) {
throw new CorruptedFrameException("Could not decrypt data from " + ctx.channel().remoteAddress() + ". Make sure the public key on the list is correct.", e);
} else {
throw new QuietException("Could not decrypt data from " + ctx.channel().remoteAddress() + ". Make sure the public key on the list is correct.");
}
}
// Parse the string we received.
String all = new String(block, StandardCharsets.US_ASCII);
String[] split = all.split("\n");
if (split.length < 5) {
throw new QuietException("Not enough fields specified in vote. This is not a NuVotifier issue. Got " + split.length + " fields, but needed 5.");
}
if (!split[0].equals("VOTE")) {
throw new QuietException("The VOTE opcode was not present. This is not a NuVotifier issue, but a bug with the server list.");
}
// Create the vote.
Vote vote = new Vote(split[1], split[2], split[3], split[4]);
list.add(vote);
// We are done, remove ourselves. Why? Sometimes, we will decode multiple vote messages.
// Netty doesn't like this, so we must remove ourselves from the pipeline. With Protocol 1,
// ending votes is a "fire and forget" operation, so this is safe.
ctx.pipeline().remove(this);
}
}
@@ -1,25 +0,0 @@
package com.vexsoftware.votifier.platform;
import com.vexsoftware.votifier.VoteHandler;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import io.netty.util.AttributeKey;
import java.security.Key;
import java.security.KeyPair;
import java.util.Map;
public interface VotifierPlugin extends VoteHandler {
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
Map<String, Key> getTokens();
KeyPair getProtocolV1Key();
LoggingAdapter getPluginLogger();
VotifierScheduler getScheduler();
default boolean isDebug() {
return false;
}
}
@@ -1,46 +0,0 @@
package com.vexsoftware.votifier.support.forwarding;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.vexsoftware.votifier.model.Vote;
import java.io.CharArrayReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public abstract class AbstractPluginMessagingForwardingSink implements ForwardingVoteSink {
public AbstractPluginMessagingForwardingSink(ForwardedVoteListener listener) {
this.listener = listener;
}
private final ForwardedVoteListener listener;
public void handlePluginMessage(byte[] message) {
String strMessage = new String(message, StandardCharsets.UTF_8);
try (CharArrayReader reader = new CharArrayReader(strMessage.toCharArray())) {
JsonReader r = new JsonReader(reader);
r.setLenient(true);
while (r.peek() != JsonToken.END_DOCUMENT) {
r.beginObject();
JsonObject o = new JsonObject();
while (r.hasNext()) {
String name = r.nextName();
if (r.peek() == JsonToken.NUMBER)
o.add(name, new JsonPrimitive(r.nextLong()));
else
o.add(name, new JsonPrimitive(r.nextString()));
}
r.endObject();
Vote v = new Vote(o);
listener.onForward(v);
}
} catch (IOException e) {
e.printStackTrace(); // Should never happen.
}
}
}
@@ -1,160 +0,0 @@
package com.vexsoftware.votifier.support.forwarding;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.platform.BackendServer;
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public abstract class AbstractPluginMessagingForwardingSource implements ForwardingVoteSource {
public AbstractPluginMessagingForwardingSource(String channel, ServerFilter serverFilter, ProxyVotifierPlugin plugin, VoteCache cache, int dumpRate) {
this.channel = channel;
this.plugin = plugin;
this.cache = cache;
this.serverFilter = serverFilter;
this.dumpRate = dumpRate;
}
protected AbstractPluginMessagingForwardingSource(String channel, ProxyVotifierPlugin plugin, VoteCache voteCache, int dumpRate) {
this(channel, null, plugin, voteCache, dumpRate);
}
protected final ProxyVotifierPlugin plugin;
protected final String channel;
protected final VoteCache cache;
protected final ServerFilter serverFilter;
private final int dumpRate;
@Override
public void forward(Vote v) {
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
for (BackendServer server : plugin.getAllBackendServers()) {
if (!serverFilter.isAllowed(server.getName())) continue;
if (!forwardSpecific(server, rawData)) {
attemptToAddToCache(v, server.getName());
} else if (plugin.isDebug()) {
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to server " + server.getName());
}
}
}
protected boolean forwardSpecific(BackendServer connection, Vote vote) {
byte[] rawData = vote.serialize().toString().getBytes(StandardCharsets.UTF_8);
return forwardSpecific(connection, rawData);
}
protected boolean forwardSpecific(BackendServer connection, Collection<Vote> votes) {
StringBuilder data = new StringBuilder();
for (Vote v : votes) {
data.append(v.serialize().toString());
}
return forwardSpecific(connection, data.toString().getBytes(StandardCharsets.UTF_8));
}
private boolean forwardSpecific(BackendServer connection, byte[] data) {
return connection.sendPluginMessage(channel, data);
}
@Override
public void halt() {
if (cache instanceof FileVoteCache) {
try {
((FileVoteCache) cache).halt();
} catch (IOException e) {
plugin.getPluginLogger().error("Unable to save cached votes, votes will be lost.", e);
}
}
}
protected void onServerConnect(BackendServer server) {
if (cache == null) return;
final Collection<Vote> cachedVotes = cache.evict(server.getName());
dumpVotesToServer(cachedVotes, server, "server '" + server + "'", failedVotes -> {
for (Vote v : failedVotes)
cache.addToCache(v, server.getName());
});
}
protected void attemptToAddToCache(Vote v, String server) {
if (cache != null) {
cache.addToCache(v, server);
if (plugin.isDebug())
plugin.getPluginLogger().info("Added to forwarding cache: " + v + " -> " + server);
} else if (plugin.isDebug())
plugin.getPluginLogger().error("Could not immediately send vote to backend, vote lost! " + v + " -> " + server);
}
protected void attemptToAddToPlayerCache(Vote v, String player) {
if (cache != null) {
cache.addToCachePlayer(v, player);
if (plugin.isDebug())
plugin.getPluginLogger().info("Added to forwarding cache: " + v + " -> (player) " + player);
} else if (plugin.isDebug())
plugin.getPluginLogger().error("Could not immediately send vote to backend, vote lost! " + v + " -> (player) " + player);
}
// returns a collection of failed votes
private void dumpVotesToServer(Collection<Vote> cachedVotes, BackendServer target, String identifier, Consumer<Collection<Vote>> cb) {
dumpVotesToServer(cachedVotes, target, identifier, 0, cb);
}
private void dumpVotesToServer(Collection<Vote> cachedVotes, BackendServer target, String identifier, int evictedAlready, Consumer<Collection<Vote>> cb) {
if (!cachedVotes.isEmpty()) {
plugin.getScheduler().delayedOnPool(() -> {
int evicted = 0;
Iterator<Vote> vi = cachedVotes.iterator();
Collection<Vote> chunk = new ArrayList<>(dumpRate);
while (vi.hasNext() && evicted < dumpRate) {
chunk.add(vi.next());
vi.remove();
}
if (forwardSpecific(target, chunk)) {
evicted += chunk.size();
if (evicted >= dumpRate && !cachedVotes.isEmpty()) {
// if we evicted everything we could but still need to evict more
dumpVotesToServer(cachedVotes, target, identifier, evictedAlready + evicted, cb);
return;
}
} else {
// so since our forwarding failed, break like we are done
cachedVotes.addAll(chunk);
}
if (plugin.isDebug()) {
plugin.getPluginLogger().info("Successfully evicted " + (evictedAlready + evicted) + " votes to " + identifier + ".");
if (!cachedVotes.isEmpty()) {
plugin.getPluginLogger().info("Held " + cachedVotes.size() + " votes for " + identifier + ".");
}
}
// cachedVotes contains any votes which have yet to be evicted
cb.accept(cachedVotes);
}, evictedAlready == 0 ? 3 : 1, TimeUnit.SECONDS);
} else {
cb.accept(cachedVotes);
}
}
protected void handlePlayerSwitch(BackendServer server, String playerName) {
if (cache == null) return;
if (!serverFilter.isAllowed(server.getName())) return;
final Collection<Vote> cachedVotes = cache.evictPlayer(playerName);
dumpVotesToServer(cachedVotes, server, "player '" + playerName + "'", failedVotes -> {
for (Vote v : failedVotes)
cache.addToCachePlayer(v, playerName);
});
}
}
@@ -1,17 +0,0 @@
package com.vexsoftware.votifier.support.forwarding;
import com.vexsoftware.votifier.model.Vote;
/**
* Represents a method to call when a vote is forwarded through the {@link ForwardingVoteSink}.
*/
public interface ForwardedVoteListener {
/**
* Called whenever a vote is forwarded from a {@link ForwardingVoteSink}
*
* @param v Vote that was forwarded
*/
void onForward(Vote v);
}
@@ -1,13 +0,0 @@
package com.vexsoftware.votifier.support.forwarding;
/**
* Represents a method at which to receive forwarded votes.
*/
public interface ForwardingVoteSink {
/**
* Stop or close any oustanding network interfaces. Occurs on the onDisable method of a plugin.
*/
void halt();
}
@@ -1,22 +0,0 @@
package com.vexsoftware.votifier.support.forwarding;
import com.vexsoftware.votifier.model.Vote;
/**
* Represents a source for forwarding votes through to servers.
*/
public interface ForwardingVoteSource {
/**
* Forwards a vote to all servers set up to receive votes.
*
* @param v Vote to forward to servers
*/
void forward(Vote v);
/**
* Stop or close any oustanding network interfaces. Occurs on the onDisable method of a plugin.
*/
void halt();
}
@@ -1,23 +0,0 @@
package com.vexsoftware.votifier.support.forwarding;
import java.util.Collection;
import java.util.Collections;
public class ServerFilter {
public ServerFilter(Collection<String> names, boolean whitelist) {
this.names = names;
this.whitelist = whitelist;
}
public ServerFilter() {
this(Collections.emptySet(), true);
}
private final Collection<String> names;
private final boolean whitelist;
public boolean isAllowed(String name) {
return names.contains(name) == whitelist;
}
}
@@ -1,169 +0,0 @@
package com.vexsoftware.votifier.support.forwarding.cache;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
import com.vexsoftware.votifier.util.GsonInst;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class FileVoteCache extends MemoryVoteCache {
private final LoggingAdapter l;
private final File cacheFile;
private final ScheduledVotifierTask saveTask;
public FileVoteCache(final VotifierPlugin plugin, File cacheFile, long voteTTL) throws IOException {
super(plugin, voteTTL);
this.cacheFile = cacheFile;
this.l = plugin.getPluginLogger();
load();
saveTask = plugin.getScheduler().repeatOnPool(() -> {
try {
save();
} catch (IOException e) {
l.error("Unable to save cached votes, votes will be lost if you restart.", e);
}
}, 3, 3, TimeUnit.MINUTES);
}
private static Set<String> keySet(JsonObject object) {
Set<String> set = new HashSet<>();
for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
set.add(entry.getKey());
}
return set;
}
private void load() throws IOException {
// Load the cache from disk
JsonObject object;
try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath(), StandardCharsets.UTF_8)) {
object = GsonInst.gson.fromJson(reader, JsonObject.class);
if (object == null) {
// When the input is not malformed but instead empty, this returns null. Simply assume it is empty.
object = new JsonObject();
}
} catch (NoSuchFileException e) {
object = new JsonObject();
}
boolean resave = false;
// First, lets figure out if we are converting from a pre2.3.6 cache
if (!object.has("players") || !object.has("servers") || !object.has("version") || object.size() != 3) {
JsonObject oldObject = object;
object = new JsonObject();
object.add("servers", oldObject);
object.add("players", new JsonObject());
object.addProperty("version", 2);
resave = true;
}
if (object.get("version").getAsInt() != 2)
throw new IllegalStateException("Could not read cache file! Unknown version '" + object.get("version").getAsInt() + "' read.");
JsonObject players = object.getAsJsonObject("players");
JsonObject servers = object.getAsJsonObject("servers");
for (String player : keySet(players)) {
playerVoteCache.put(player, readVotes(players.getAsJsonArray(player)));
}
for (String server : keySet(servers)) {
voteCache.put(server, readVotes(servers.getAsJsonArray(server)));
}
if (resave) {
File replacementFile;
for (int i = 0; ; i++) {
replacementFile = new File(cacheFile.getParentFile(), cacheFile.getName() + ".bak." + i);
if (!replacementFile.exists())
break;
}
if (!cacheFile.renameTo(replacementFile)) {
l.error("Backup movement failed! Will not save.");
return;
}
l.warn("Saving new vote cache format to file - backup moved to " + replacementFile.getAbsolutePath());
save();
}
}
private Collection<VoteWithRecordedTimestamp> readVotes(JsonArray voteArray) {
Collection<VoteWithRecordedTimestamp> votes = new HashSet<>(voteArray.size());
for (int i = 0; i < voteArray.size(); i++) {
JsonObject voteObject = voteArray.get(i).getAsJsonObject();
VoteWithRecordedTimestamp v = new VoteWithRecordedTimestamp(voteObject);
if (hasTimedOut(v))
l.warn("Purging out of date vote.", v);
else
votes.add(v);
}
return votes;
}
public void save() throws IOException {
JsonObject votesObject = new JsonObject();
cacheLock.lock();
try {
votesObject.addProperty("version", 2);
votesObject.add("players", serializeMap(playerVoteCache));
votesObject.add("servers", serializeMap(voteCache));
} finally {
cacheLock.unlock();
}
try (BufferedWriter writer = Files.newBufferedWriter(cacheFile.toPath(), StandardCharsets.UTF_8)) {
GsonInst.gson.toJson(votesObject, writer);
}
}
public JsonObject serializeMap(Map<String, Collection<VoteWithRecordedTimestamp>> map) {
JsonObject o = new JsonObject();
Iterator<Map.Entry<String, Collection<VoteWithRecordedTimestamp>>> entryItr = map.entrySet().iterator();
while (entryItr.hasNext()) {
Map.Entry<String, Collection<VoteWithRecordedTimestamp>> entry = entryItr.next();
JsonArray array = new JsonArray();
Iterator<VoteWithRecordedTimestamp> voteItr = entry.getValue().iterator();
while (voteItr.hasNext()) {
VoteWithRecordedTimestamp vote = voteItr.next();
// if the vote is no longer valid, notify and remove
if (hasTimedOut(vote)) {
l.warn("Purging out of date vote.", vote);
voteItr.remove();
} else {
array.add(vote.serialize());
}
}
// if, during our iteration, we TTL invalidated all of the votes
if (entry.getValue().isEmpty())
entryItr.remove();
o.add(entry.getKey(), array);
}
return o;
}
public void halt() throws IOException {
saveTask.cancel();
save();
}
}
@@ -1,175 +0,0 @@
package com.vexsoftware.votifier.support.forwarding.cache;
import com.google.gson.JsonObject;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by Joe Hirschfeld on 10/20/2015.
*/
public class MemoryVoteCache implements VoteCache {
private final LoggingAdapter l;
private final long voteTTL;
protected final Map<String, Collection<VoteWithRecordedTimestamp>> voteCache;
protected final Map<String, Collection<VoteWithRecordedTimestamp>> playerVoteCache;
protected final ReentrantLock cacheLock = new ReentrantLock();
private final ScheduledVotifierTask sweepTask;
public MemoryVoteCache(VotifierPlugin p, long voteTTL) {
voteCache = new HashMap<>();
playerVoteCache = new HashMap<>();
this.voteTTL = voteTTL;
this.l = p.getPluginLogger();
this.sweepTask = p.getScheduler().repeatOnPool(this::sweep, 12, 12, TimeUnit.HOURS);
}
@Override
public Collection<String> getCachedServers() {
cacheLock.lock();
try {
return Collections.unmodifiableCollection(new ArrayList<>(voteCache.keySet()));
} finally {
cacheLock.unlock();
}
}
@Override
public void addToCache(Vote v, String server) {
if (server == null) throw new NullPointerException();
cacheLock.lock();
try {
voteCache.computeIfAbsent(server, k -> new HashSet<>()).add(new VoteWithRecordedTimestamp(v));
} finally {
cacheLock.unlock();
}
}
@Override
public void addToCachePlayer(Vote v, String player) {
if (player == null) throw new NullPointerException();
cacheLock.lock();
try {
playerVoteCache.computeIfAbsent(player, k -> new HashSet<>()).add(new VoteWithRecordedTimestamp(v));
} finally {
cacheLock.unlock();
}
}
@Override
public Collection<Vote> evictPlayer(String player) {
if (player == null) throw new NullPointerException();
cacheLock.lock();
try {
Collection<VoteWithRecordedTimestamp> playerVotes = playerVoteCache.remove(player);
if (playerVotes != null) {
return new HashSet<>(playerVotes);
} else {
return Collections.emptySet();
}
} finally {
cacheLock.unlock();
}
}
@Override
public Collection<Vote> evict(String server) {
if (server == null) throw new NullPointerException();
cacheLock.lock();
try {
Collection<VoteWithRecordedTimestamp> serverVotes = voteCache.remove(server);
if (serverVotes != null) {
return new HashSet<>(serverVotes);
} else {
return Collections.emptySet();
}
} finally {
cacheLock.unlock();
}
}
public void sweep() {
cacheLock.lock();
try {
sweep(voteCache);
sweep(playerVoteCache);
} finally {
cacheLock.unlock();
}
}
private void sweep(Map<String, Collection<VoteWithRecordedTimestamp>> m) {
for (Map.Entry<String, Collection<VoteWithRecordedTimestamp>> entry : m.entrySet()) {
Iterator<VoteWithRecordedTimestamp> vi = entry.getValue().iterator();
while (vi.hasNext()) {
VoteWithRecordedTimestamp v = vi.next();
if (hasTimedOut(v)) {
l.warn("Purging out of date vote.", v);
vi.remove();
}
}
}
}
protected boolean hasTimedOut(VoteWithRecordedTimestamp v) {
if (voteTTL == -1) return false;
long daysAsMillis = TimeUnit.DAYS.toMillis(voteTTL);
return v.recorded + daysAsMillis < System.currentTimeMillis();
}
static class VoteWithRecordedTimestamp extends Vote {
private final long recorded;
VoteWithRecordedTimestamp(Vote vote) {
super(vote);
this.recorded = System.currentTimeMillis();
}
VoteWithRecordedTimestamp(JsonObject object) {
super(object);
if (object.has("recorded")) {
this.recorded = object.get("recorded").getAsLong();
} else {
// Assign the current time, in the hope that the stale votes will eventually get purged.
this.recorded = System.currentTimeMillis();
}
}
@Override
public JsonObject serialize() {
JsonObject object = super.serialize();
object.addProperty("recorded", this.recorded);
return object;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
VoteWithRecordedTimestamp that = (VoteWithRecordedTimestamp) o;
return recorded == that.recorded;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (int) (recorded ^ (recorded >>> 32));
return result;
}
}
}
@@ -1,56 +0,0 @@
package com.vexsoftware.votifier.support.forwarding.cache;
import com.vexsoftware.votifier.model.Vote;
import java.util.Collection;
import java.util.Collections;
/**
* Represents a method of caching votes for forwarding to a server that it was not previously capable of sending.
*/
public interface VoteCache {
/**
* Returns the names of all server in which the cache is holding votes for
*
* @return Collection of all cached servers
*/
Collection<String> getCachedServers();
/**
* Adds a vote to the vote cache for later sending
*
* @param v Vote to add to cache
* @param server Server to add vote under
*/
void addToCache(Vote v, String server);
/**
* Adds a vote to the vote cache for later sending, specifically keyed to a player. This is for the instance when the
* player is rewarded, however is not online any servers able to recieve said vote.
*
* @param v Vote to add to the cache
* @param player Server to add vote under
*/
default void addToCachePlayer(Vote v, String player) {
}
/**
* Evicts all votes from the vote cache and returns a collection of vote objects
*
* @param server Server name of which to evict the votes from the cache
* @return A collection of all votes which were previously in the cache under the server
*/
Collection<Vote> evict(String server);
/**
* Evicts all votes from the vote cache for a specific player not assigned to a server and returns a collection of
* vote objects
*
* @param player Player name of the votes to evict from the cache
* @return A collection of all votes which were previously in the cache under the player
*/
default Collection<Vote> evictPlayer(String player) {
return Collections.EMPTY_LIST;
}
}
@@ -1,128 +0,0 @@
package com.vexsoftware.votifier.support.forwarding.proxy;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2Encoder;
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierResponseHandler;
import com.vexsoftware.votifier.model.Vote;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class ProxyForwardingVoteSource implements ForwardingVoteSource {
private static final int MAX_RETRIES = 5;
private final VotifierPlugin plugin;
private final Supplier<Bootstrap> nettyBootstrap;
private final List<BackendServer> backendServers;
private final VoteCache voteCache;
private static final StringDecoder STRING_DECODER = new StringDecoder(StandardCharsets.US_ASCII);
public ProxyForwardingVoteSource(VotifierPlugin plugin, Supplier<Bootstrap> nettyBootstrap, List<BackendServer> backendServers, VoteCache voteCache) {
this.plugin = plugin;
this.nettyBootstrap = nettyBootstrap;
this.backendServers = backendServers;
this.voteCache = voteCache;
}
@Override
public void forward(Vote v) {
for (final BackendServer server : backendServers) {
forwardVote(server, v, 0);
}
}
private int fib(int t) {
if (t <= 1) return 1;
return fib(t - 2) + fib(t - 1);
}
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
nettyBootstrap.get()
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) {
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
channel.pipeline().addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
channel.pipeline().addLast(STRING_DECODER);
channel.pipeline().addLast(new VotifierProtocol2Encoder(server.key));
channel.pipeline().addLast(new VotifierProtocol2HandshakeHandler(v, new VotifierResponseHandler() {
@Override
public void onSuccess() {
if (plugin.isDebug()) {
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
}
}
@Override
public void onFailure(Throwable error) {
handleFailure(server, v, error, tries);
}
}, plugin));
}
})
.connect(server.address)
.addListener((ChannelFutureListener) future -> {
if (!future.isSuccess()) {
handleFailure(server, v, future.cause(), tries);
}
});
}
private void handleFailure(final BackendServer server, final Vote v, Throwable cause, final int tries) {
int nextDelay = fib(tries + 1);
boolean willRetry = tries < MAX_RETRIES;
String msg = "Unable to send vote to " + server.address + ".";
if (willRetry) {
msg += " Will retry sending in " + nextDelay + " second(s).";
} else {
if (voteCache == null) {
msg += " This vote will be lost!";
} else {
voteCache.addToCache(v, server.name);
}
}
if (plugin.isDebug()) {
plugin.getPluginLogger().error(msg, cause);
} else {
plugin.getPluginLogger().error(msg);
}
if (willRetry) {
plugin.getScheduler().delayedOnPool(() -> forwardVote(server, v, tries + 1), nextDelay, TimeUnit.SECONDS);
}
}
@Override
public void halt() {
}
public static class BackendServer {
private final String name;
private final InetSocketAddress address;
private final Key key;
public BackendServer(String name, InetSocketAddress address, Key key) {
this.name = name;
this.address = address;
this.key = key;
}
}
}
@@ -1,29 +0,0 @@
package com.vexsoftware.votifier.support.forwarding.proxy.client;
import com.vexsoftware.votifier.model.Vote;
public class VoteRequest {
private final String challenge;
private final Vote vote;
public VoteRequest(String challenge, Vote vote) {
this.challenge = challenge;
this.vote = vote;
}
public String getChallenge() {
return challenge;
}
public Vote getVote() {
return vote;
}
@Override
public String toString() {
return "VoteRequest{" +
"challenge='" + challenge + '\'' +
", vote=" + vote +
'}';
}
}
@@ -1,46 +0,0 @@
package com.vexsoftware.votifier.support.forwarding.proxy.client;
import com.google.gson.JsonObject;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import javax.crypto.Mac;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;
public class VotifierProtocol2Encoder extends MessageToByteEncoder<VoteRequest> {
private static final short MAGIC = 0x733A;
private final Key key;
public VotifierProtocol2Encoder(Key key) {
this.key = key;
}
@Override
protected void encode(ChannelHandlerContext ctx, VoteRequest req, ByteBuf buf) throws Exception {
JsonObject object = new JsonObject();
JsonObject payloadObject = req.getVote().serialize();
payloadObject.addProperty("challenge", req.getChallenge());
String payload = payloadObject.toString();
object.addProperty("payload", payload);
// Generate the MAC
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
mac.update(payload.getBytes(StandardCharsets.UTF_8));
String computed = Base64.getEncoder().encodeToString(mac.doFinal());
object.addProperty("signature", computed);
// JSON message is ready for encoding.
String finalMessage = object.toString();
buf.writeShort(MAGIC);
buf.writeShort(finalMessage.length());
ByteBuf messageBytes = Unpooled.copiedBuffer(finalMessage, StandardCharsets.UTF_8);
buf.writeBytes(messageBytes);
messageBytes.release();
}
}
@@ -1,42 +0,0 @@
package com.vexsoftware.votifier.support.forwarding.proxy.client;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.model.Vote;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.CorruptedFrameException;
public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandler<String> {
private final Vote toSend;
private final VotifierResponseHandler responseHandler;
private final VotifierPlugin nuVotifier;
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, VotifierPlugin nuVotifier) {
this.toSend = toSend;
this.responseHandler = responseHandler;
this.nuVotifier = nuVotifier;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) {
String[] handshakeContents = s.split(" ");
if (handshakeContents.length != 3) {
throw new CorruptedFrameException("Handshake is not valid.");
}
VoteRequest request = new VoteRequest(handshakeContents[2], toSend);
if (nuVotifier.isDebug()) {
nuVotifier.getPluginLogger().info("Sent request: " + request.toString());
}
ctx.writeAndFlush(request);
ctx.pipeline().addLast(new VotifierProtocol2ResponseHandler(responseHandler));
ctx.pipeline().remove(this);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
responseHandler.onFailure(cause);
ctx.close();
}
}
@@ -1,33 +0,0 @@
package com.vexsoftware.votifier.support.forwarding.proxy.client;
import com.google.gson.JsonObject;
import com.vexsoftware.votifier.util.GsonInst;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandler<String> {
private final VotifierResponseHandler responseHandler;
public VotifierProtocol2ResponseHandler(VotifierResponseHandler responseHandler) {
this.responseHandler = responseHandler;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
JsonObject object = GsonInst.gson.fromJson(msg, JsonObject.class);
String status = object.get("status").getAsString();
if (status.equals("ok")) {
responseHandler.onSuccess();
} else {
responseHandler.onFailure(new Exception("Remote server error: " + object.get("cause").getAsString() +
": " + object.get("error").getAsString()));
}
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
responseHandler.onFailure(cause);
ctx.close();
}
}
@@ -1,7 +0,0 @@
package com.vexsoftware.votifier.support.forwarding.proxy.client;
public interface VotifierResponseHandler {
void onSuccess();
void onFailure(Throwable error);
}
@@ -1,62 +0,0 @@
package com.vexsoftware.votifier.util;
import com.vexsoftware.votifier.model.Vote;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ArgsToVote {
private static final Pattern argumentPattern = Pattern.compile("([a-zA-Z]+)=(\\S*)");
public static Vote parse(String[] arguments) {
return parse(arguments, null);
}
public static Vote parse(String[] arguments, Map<String, String> additionalArgs) {
String serviceName = "TestVote";
String username = null;
String address = "127.0.0.1";
long localTimestamp = System.currentTimeMillis();
String timestamp = Long.toString(localTimestamp, 10);
for (String s : arguments) {
Matcher m = argumentPattern.matcher(s);
if (m.matches()) {
String key = m.group(1).toLowerCase();
String v = m.group(2);
switch (key) {
case "servicename":
serviceName = v;
break;
case "username":
if (v.length() > 16)
throw new IllegalArgumentException("Illegal username - must be less than 16 characters long.");
username = v;
break;
case "address":
address = v;
break;
case "timestamp":
timestamp = v;
break;
default:
if (additionalArgs != null)
additionalArgs.put(key, v);
break;
}
} else {
if (s.length() > 16)
throw new IllegalArgumentException("Illegal username - must be less than 16 characters long.");
username = s; // ezpz
}
}
if (username == null)
throw new IllegalArgumentException("Username not specified!");
return new Vote(serviceName, username, address, timestamp);
}
}
@@ -1,17 +0,0 @@
package com.vexsoftware.votifier.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class IOUtil {
public static byte[] readAllBytes(InputStream from) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(8192);
byte[] buf = new byte[8192];
int read;
while ((read = from.read(buf)) != -1) {
out.write(buf, 0, read);
}
return out.toByteArray();
}
}
@@ -1,11 +0,0 @@
package com.vexsoftware.votifier.util;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
public class KeyCreator {
public static Key createKeyFrom(String token) {
return new SecretKeySpec(token.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
}
}
@@ -1,15 +0,0 @@
package com.vexsoftware.votifier.util;
public class QuietException extends Exception {
public QuietException() {
}
public QuietException(String s) {
super(s);
}
@Override
public synchronized Throwable fillInStackTrace() {
return null;
}
}
@@ -1,79 +0,0 @@
package com.vexsoftware.votifier.util.standalone;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.platform.JavaUtilLogger;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.ScheduledExecutorServiceVotifierScheduler;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import java.net.InetSocketAddress;
import java.security.Key;
import java.security.KeyPair;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.logging.Logger;
public class StandaloneVotifierPlugin implements VotifierPlugin {
private final Map<String, Key> tokens;
private final VoteReceiver receiver;
private final KeyPair v1Key;
private final InetSocketAddress bind;
private final VotifierScheduler scheduler;
private VotifierServerBootstrap bootstrap;
public StandaloneVotifierPlugin(Map<String, Key> tokens, VoteReceiver receiver, KeyPair v1Key, InetSocketAddress bind) {
this.receiver = receiver;
this.bind = bind;
this.tokens = Collections.unmodifiableMap(new HashMap<>(tokens));
this.v1Key = v1Key;
this.scheduler = new ScheduledExecutorServiceVotifierScheduler(Executors.newScheduledThreadPool(1));
}
public void start() {
start(o -> {});
}
public void start(Consumer<Throwable> error) {
if (bootstrap != null) {
bootstrap.shutdown();
}
this.bootstrap = new VotifierServerBootstrap(bind.getHostString(), bind.getPort(), this, v1Key == null);
this.bootstrap.start(error);
}
@Override
public Map<String, Key> getTokens() {
return tokens;
}
@Override
public KeyPair getProtocolV1Key() {
return v1Key;
}
@Override
public LoggingAdapter getPluginLogger() {
return new JavaUtilLogger(Logger.getAnonymousLogger());
}
@Override
public VotifierScheduler getScheduler() {
return scheduler;
}
@Override
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception {
receiver.onVote(vote);
}
@Override
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
receiver.onException(throwable);
}
}
@@ -1,8 +0,0 @@
package com.vexsoftware.votifier.util.standalone;
import com.vexsoftware.votifier.model.Vote;
public interface VoteReceiver {
void onVote(Vote vote) throws Exception;
void onException(Throwable throwable);
}
@@ -1,57 +0,0 @@
package com.vexsoftware.votifier.util.standalone;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
import com.vexsoftware.votifier.util.KeyCreator;
import java.io.File;
import java.net.InetSocketAddress;
import java.security.Key;
import java.security.KeyPair;
import java.security.interfaces.RSAKey;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class VotifierServerBuilder {
private final Map<String, Key> keyMap = new HashMap<>();
private KeyPair v1Key;
private InetSocketAddress bind;
private VoteReceiver receiver;
public VotifierServerBuilder addToken(String service, String token) {
Objects.requireNonNull(service, "service");
Objects.requireNonNull(token, "key");
keyMap.put(service, KeyCreator.createKeyFrom(token));
return this;
}
public VotifierServerBuilder v1Key(KeyPair v1Key) {
this.v1Key = Objects.requireNonNull(v1Key, "v1Key");
if (!(v1Key.getPrivate() instanceof RSAKey)) {
throw new IllegalArgumentException("Provided key is not an RSA key.");
}
return this;
}
public VotifierServerBuilder v1KeyFolder(File file) throws Exception {
this.v1Key = RSAIO.load(Objects.requireNonNull(file, "file"));
return this;
}
public VotifierServerBuilder bind(InetSocketAddress bind) {
this.bind = Objects.requireNonNull(bind, "bind");
return this;
}
public VotifierServerBuilder receiver(VoteReceiver receiver) {
this.receiver = Objects.requireNonNull(receiver, "receiver");
return this;
}
public StandaloneVotifierPlugin create() {
Objects.requireNonNull(bind, "need an address to bind to");
Objects.requireNonNull(receiver, "need a receiver for votes");
return new StandaloneVotifierPlugin(keyMap, receiver, v1Key, bind);
}
}
@@ -1,107 +0,0 @@
package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import com.vexsoftware.votifier.util.KeyCreator;
import io.netty.channel.Channel;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class TestVotifierPlugin implements VotifierPlugin {
private static final byte[] PUBLIC_KEY;
private static final byte[] PRIVATE_KEY;
static byte[] r(String u) throws Exception {
URL resourceUrl = TestVotifierPlugin.class.getResource(u);
Path resourcePath = Paths.get(resourceUrl.toURI());
return Base64.getDecoder().decode(new String(Files.readAllBytes(resourcePath), StandardCharsets.UTF_8));
}
static {
try {
PUBLIC_KEY = r("/public.key");
PRIVATE_KEY = r("/private.key");
} catch (Exception e) {
throw new AssertionError(e);
}
}
public static final TestVotifierPlugin I = new TestVotifierPlugin();
public static TestVotifierPlugin getI() {
return I;
}
private final Map<String, Key> keyMap = new HashMap<>();
private final KeyPair keyPair;
public TestVotifierPlugin() {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(
PUBLIC_KEY);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
PRIVATE_KEY);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
keyPair = new KeyPair(publicKey, privateKey);
} catch (Exception e) {
throw new AssertionError(e);
}
keyMap.put("default", KeyCreator.createKeyFrom("test"));
}
@Override
public Map<String, Key> getTokens() {
return keyMap;
}
@Override
public KeyPair getProtocolV1Key() {
return keyPair;
}
@Override
public LoggingAdapter getPluginLogger() {
return null;
}
@Override
public VotifierScheduler getScheduler() {
return null;
}
@Override
public boolean isDebug() {
return true;
}
public void specificKeysOnly() {
keyMap.clear();
keyMap.put("Test", KeyCreator.createKeyFrom("test"));
}
public void restoreDefault() {
keyMap.clear();
keyMap.put("default", KeyCreator.createKeyFrom("test"));
}
@Override
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception {
}
}
@@ -1,31 +0,0 @@
package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
public class VoteUtil {
public static byte[] encodePOJOv1(Vote vote) throws Exception {
return encodePOJOv1(vote, TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
}
public static byte[] encodePOJOv1(Vote vote, PublicKey key) throws Exception {
List<String> ordered = new ArrayList<>();
ordered.add("VOTE");
ordered.add(vote.getServiceName());
ordered.add(vote.getUsername());
ordered.add(vote.getAddress());
ordered.add(vote.getTimeStamp());
StringBuilder builder = new StringBuilder();
for (String s : ordered) {
builder.append(s).append('\n'); // naive join needed!
}
return RSA.encrypt(builder.toString().getBytes(StandardCharsets.US_ASCII), key);
}
}
@@ -1,85 +0,0 @@
package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
import com.vexsoftware.votifier.util.QuietException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import static org.junit.jupiter.api.Assertions.*;
public class VotifierProtocol1DecoderTest {
private static final VotifierSession SESSION = new VotifierSession();
private EmbeddedChannel createChannel() {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocol1Decoder());
channel.attr(VotifierSession.KEY).set(SESSION);
channel.attr(VotifierPlugin.KEY).set(TestVotifierPlugin.getI());
return channel;
}
@Test
public void testSuccessfulDecode() throws Exception {
Vote votePojo = new Vote("Test", "test", "test", "test");
// Send the vote
EmbeddedChannel channel = createChannel();
byte[] encrypted = VoteUtil.encodePOJOv1(votePojo);
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
assertTrue(channel.writeInbound(encryptedByteBuf));
Object presumedVote = channel.readInbound();
assertEquals(votePojo, presumedVote);
assertFalse(channel.finish());
}
private void verifyFailure(String bad) throws Exception {
// Send the bad vote
EmbeddedChannel channel = createChannel();
byte[] encrypted = RSA.encrypt(bad.getBytes(StandardCharsets.UTF_8), TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
assertThrows(DecoderException.class, () -> channel.writeInbound(encryptedByteBuf));
channel.close();
}
@Test
public void testFailureDecodeMissingField() throws Exception {
verifyFailure("VOTE\nTest\ntest\ntest"); // missing field
}
@Test
public void testFailureDecodeBadOpcode() throws Exception {
verifyFailure("TEST\nTest\ntest\ntest\ntest\n");
}
@Test
public void testFailureDecodeBadRsa() throws Exception {
// Decode our bad RSA key
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(TestVotifierPlugin.r("/bad_public.key"));
PublicKey badPublicKey = keyFactory.generatePublic(publicKeySpec);
// Send the bad vote
EmbeddedChannel channel = createChannel();
byte[] encrypted = VoteUtil.encodePOJOv1(new Vote("Test", "test", "test", "test"), badPublicKey);
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
assertThrows(DecoderException.class, ()->channel.writeInbound(encryptedByteBuf));
channel.close();
}
}
@@ -1,137 +0,0 @@
package com.vexsoftware.votifier.net.protocol;
import com.google.gson.JsonObject;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.GsonInst;
import com.vexsoftware.votifier.util.KeyCreator;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import javax.crypto.Mac;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.*;
public class VotifierProtocol2DecoderTest {
private static final VotifierSession SESSION = new VotifierSession();
private EmbeddedChannel createChannel() {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocol2Decoder());
channel.attr(VotifierSession.KEY).set(SESSION);
channel.attr(VotifierPlugin.KEY).set(TestVotifierPlugin.getI());
return channel;
}
private void sendVote(Vote vote, Key key, boolean expectSuccess) throws Exception {
// Create a well-formed request
EmbeddedChannel channel = createChannel();
JSONObject object = new JSONObject();
JsonObject payload = vote.serialize();
payload.addProperty("challenge", SESSION.getChallenge());
String payloadEncoded = GsonInst.gson.toJson(payload);
object.put("payload", payloadEncoded);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
object.put("signature",
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
if (expectSuccess) {
assertTrue(channel.writeInbound(object.toString()));
assertEquals(vote, channel.readInbound());
assertFalse(channel.finish());
} else {
try {
channel.writeInbound(object.toString());
} finally {
channel.close();
}
}
}
@Test
public void testSuccessfulDecode() throws Exception {
sendVote(new Vote("Test", "test", "test", "0"), TestVotifierPlugin.getI().getTokens().get("default"), true);
}
@Test
public void testFailureDecodeBadPacket() {
// Create a well-formed request
EmbeddedChannel channel = createChannel();
Vote vote = new Vote("Test", "test", "test", "0");
JSONObject object = new JSONObject();
JsonObject payload = vote.serialize();
payload.addProperty("challenge", SESSION.getChallenge());
object.put("payload", GsonInst.gson.toJson(payload));
// We "forget" the signature.
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
channel.close();
}
@Test
public void testFailureDecodeBadVoteField() throws Exception {
// Create a well-formed request
EmbeddedChannel channel = createChannel();
Vote vote = new Vote("Test", "test", "test", "0");
JSONObject object = new JSONObject();
JsonObject payload = vote.serialize();
String payloadEncoded = GsonInst.gson.toJson(payload);
// We "forget" the challenge.
object.put("payload", payloadEncoded);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(TestVotifierPlugin.getI().getTokens().get("default"));
object.put("signature",
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
channel.close();
}
@Test
public void testFailureDecodeBadChallenge() throws Exception {
// Create a well-formed request
EmbeddedChannel channel = createChannel();
Vote vote = new Vote("Test", "test", "test", "0");
JSONObject object = new JSONObject();
JsonObject payload = vote.serialize();
// We provide the wrong challenge.
payload.addProperty("challenge", "not a challenge for me");
object.put("payload", payload.toString());
String payloadEncoded = GsonInst.gson.toJson(payload);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(TestVotifierPlugin.getI().getTokens().get("default"));
object.put("signature",
Base64.getEncoder().encode(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
channel.close();
}
@Test
public void testFailureDecodeNonExistentKey() throws Exception {
TestVotifierPlugin.getI().specificKeysOnly();
Vote vote = new Vote("Bad Service", "test", "test", "0");
assertThrows(DecoderException.class, () -> sendVote(vote, TestVotifierPlugin.getI().getTokens().get("Test"), false));
TestVotifierPlugin.getI().restoreDefault();
}
@Test
public void testFailureDecodeBadSignature() {
Vote vote = new Vote("Bad Service", "test", "test", "0");
assertThrows(CorruptedFrameException.class, () -> sendVote(vote, KeyCreator.createKeyFrom("BadKey"), false));
}
}
@@ -1,93 +0,0 @@
package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.QuietException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class VotifierProtocolDifferentiatorTest {
@Test
public void v1Test() {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, true));
VotifierSession session = new VotifierSession();
channel.attr(VotifierSession.KEY).set(session);
ByteBuf test = Unpooled.buffer(256);
for (int i = 0; i < 256; i++) {
test.writeByte(0);
}
channel.writeInbound(test);
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
channel.close();
}
@Test
public void v2Test() {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
VotifierSession session = new VotifierSession();
channel.attr(VotifierSession.KEY).set(session);
ByteBuf test = Unpooled.buffer();
test.writeShort(0x733A);
channel.writeInbound(test);
assertEquals(VotifierSession.ProtocolVersion.TWO, session.getVersion());
channel.close();
}
@Test
public void failIfv1NotSupported() {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
VotifierSession session = new VotifierSession();
channel.attr(VotifierSession.KEY).set(session);
ByteBuf test = Unpooled.buffer(256);
for (int i = 0; i < 256; i++) {
test.writeByte(0);
}
assertThrows(DecoderException.class, () -> channel.writeInbound(test));
channel.close();
}
@Test
public void failOnBadPacketTest() {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
ByteBuf buf = Unpooled.buffer();
for (int i = 0; i < 3; i++) {
buf.writeByte(0);
}
assertThrows(DecoderException.class, () -> channel.writeInbound(buf));
channel.close();
}
@Test
public void tryIdentifyRealVotev1() throws Exception {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, true));
VotifierSession session = new VotifierSession();
channel.attr(VotifierSession.KEY).set(session);
Vote votePojo = new Vote("Test", "test", "test", "test");
byte[] encrypted = VoteUtil.encodePOJOv1(votePojo);
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
channel.writeInbound(encryptedByteBuf);
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
channel.close();
}
}
@@ -1,49 +0,0 @@
package com.vexsoftware.votifier.support.forwarding;
import com.vexsoftware.votifier.model.Vote;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class PMForwardingSinkTest {
@Test
public void testSuccessfulMultiDecode() throws Exception {
List<Vote> receivedVotes = new ArrayList<>();
ForwardedVoteListener vl = receivedVotes::add;
List<Vote> sentVotes = new ArrayList<>(Arrays.asList(
new Vote("serviceA", "usernameA", "1.1.1.1", "1546300800"),
new Vote("serviceB", "usernameBBBBBBB", "1.2.23.4", "1514764800")
));
StringBuilder message = new StringBuilder();
for (Vote v : sentVotes) {
message.append(v.serialize());
}
byte[] messageBytes = message.toString().getBytes(StandardCharsets.UTF_8);
System.out.println(message.toString());
AbstractPluginMessagingForwardingSink sink = new AbstractPluginMessagingForwardingSink(vl) {
@Override
public void halt() {
}
};
sink.handlePluginMessage(messageBytes);
assertEquals(sentVotes.size(), receivedVotes.size());
for (int i = 0; i < receivedVotes.size(); i++) {
assertEquals(sentVotes.get(i), receivedVotes.get(i));
}
}
}
@@ -1,108 +0,0 @@
package com.vexsoftware.votifier.util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.vexsoftware.votifier.model.Vote;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.lang.reflect.Method;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
public class ArgsToVoteTest {
@Test
void handlesInvalidRequest() {
assertThrows(IllegalArgumentException.class, () -> {
ArgsToVote.parse(new String[0]);
});
}
@Test
void handlesSingleUsername() {
Vote vote = ArgsToVote.parse(new String[] { "dummy_user" });
assertEquals("dummy_user", vote.getUsername());
}
@Test
void handlesSingleUsernameTooLong() {
assertThrows(IllegalArgumentException.class, () -> {
ArgsToVote.parse(new String[] { "AVeryVeryVeryLongUsername" });
});
}
@ParameterizedTest
@ValueSource(strings = { "servicename", "address", "timestamp" })
void handlesAdditionalKeywordArguments(String key) throws Exception {
Vote vote = ArgsToVote.parse(new String[] { "username=abc123", key + "=dummy" });
// Find the getter on the Vote object
for (Method method : Vote.class.getDeclaredMethods()) {
if (method.getName().toLowerCase(Locale.ENGLISH).equals("get" + key) && method.getParameterCount() == 0) {
assertEquals("dummy", method.invoke(vote));
return;
}
}
fail("Unable to find appropriate getter - this is a bug");
}
@Test
void handlesUsernameKeywordArgument() {
Vote vote = ArgsToVote.parse(new String[] { "username=dummy_user" });
assertEquals("dummy_user", vote.getUsername());
}
@ParameterizedTest
@ValueSource(strings = { "servicename", "address", "timestamp" })
void handlesMixedUsernameAndKeywordArgument(String key) throws Exception {
Vote vote = ArgsToVote.parse(new String[] { "abc123", key + "=dummy" });
assertEquals("abc123", vote.getUsername());
// Find the getter on the Vote object
for (Method method : Vote.class.getDeclaredMethods()) {
if (method.getName().toLowerCase(Locale.ENGLISH).equals("get" + key) && method.getParameterCount() == 0) {
assertEquals("dummy", method.invoke(vote));
return;
}
}
fail("Unable to find appropriate getter - this is a bug");
}
@ParameterizedTest
@MethodSource("powerSetArgs")
void handlesMixedKeywordArguments(Set<String> keys) throws Exception {
String[] args = new String[keys.size() + 1];
args[0] = "abc123";
for (int i = 0; i < keys.size(); i++) {
args[i + 1] = ImmutableList.copyOf(keys).get(i) + "=dummy";
}
Vote vote = ArgsToVote.parse(args);
assertEquals("abc123", vote.getUsername());
// Find the getters on the Vote object
outer: for (String key : keys) {
for (Method method : Vote.class.getDeclaredMethods()) {
if (method.getName().toLowerCase(Locale.ENGLISH).equals("get" + key) && method.getParameterCount() == 0) {
assertEquals("dummy", method.invoke(vote));
continue outer;
}
}
fail("Unable to find appropriate getter - this is a bug");
}
}
private static Stream<Set<String>> powerSetArgs() {
return Sets.powerSet(ImmutableSet.of("servicename", "address", "timestamp")).stream();
}
}
-1
View File
@@ -1 +0,0 @@
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArrOsFuGPoHDZEFRHXriZRqrUXXjUOhVabC0Xu+WPtVbQrvNngN0igo0ZJIZELflhsFNBUJAnREo+wColfDg1rk2juEUYDt+gRzrN3WiqgzZolF3E4Dg4LrXh6cPuu4AeOZW9CtjXt2JwPBhlWLZb2RFhA6FziFLeMBa42/5VLFCQAmiuuU3OCiBskR3Jz/E5zqc7V/j247OMm+WRJ/k8OFTEFB+zFINOZY1ud3ltwkzQIPi1luGNW8Yt8+CEg/6hDiVlpFwgsazt4uKI/UfU5oP0kAKjQUfFGvNaBtJ0RlsBeJ7VDpOfIG6dDb8pe4IaA9aJLVcXWEn30pHsyYl2NwIDAQAB
-1
View File
@@ -1 +0,0 @@
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCd5osoNvoLM52pN8U15QkRXpGVxk0WaoNLWgN24pnW30FIAO3bp8qD2FXJzX2hvw43LIwudVEbwmdzeytVWDNuigLqEsJJsXCHh9ORKjGVHvS240r6idfxJ139Jz2d3oSZG8acHdHgs/H/M98O8lz9Zb2KHtm+rxpiwsV2SdhMtfMDJmRx4p0JklgJ8TV1ZApoypbGtRKXHOirVSs6zfgaYaeglUNITAgsZZNZk+j9Pp572FxyYryxkUaWVn09RAD07XvWKJc3f7NBElA2LpuZXTYJh3BXaI/N05tdgPej5o99PXaA2UMhdqo778zoQoeWzOvbNMUqZROqGW5WDwhdAgMBAAECggEAM8xcXB3k2uXc05UrCaoP1nXadnKt+SuJ1J87wbVGWDm21aMQZF1fzu6Z/fB+RSBCzpC7j8YGjH+ypiyCo6WigaGeoSD0QW6HBr7lBZr81vlnt9NKVnP/tel/KLvFt9M2Vm/JMI+LuRXuzhQ7GVEidjH5AqVqMuaIVlukoVBKN6d20KihcHeCjOZLAoL727QYGrHNr0eH9wQpgMzj3A51ubi5ma3bDvnSvN4ZoP3BhmDTRVnHEh4hx/XFYELi3RDGN2SDjegNXlZ+e03BHCGRryOCuwY1eua78gkrWN8sr3QAdWhEN/Pdwq0BpVsdsbyPzmgoamAMmBbKyBnEJxuvlQKBgQDtLQIhFVYA7vNtpwnXV3ahyEPCNf+tLJYsLYjKWRpoTw6bI3FfgMAEPGbDXLsmLi37mvRmCJpoU9j4nfRd/umoEIW6xuaYDdGRpyaiOlkEXyJd0n3uv/3mF7VtyltrPK3Mt2Gb7kGaZqe07B4a5UC02CCvpKqpbFfC0XxHP32GkwKBgQCqbs2D/pNcdA9iHm+AhX/oylK+BfiruTvLx/c2wOFleQOiNZUZCC6zELIDzuQXPlk9mlNshnciIItcjnyLGkDr2lvJpt6Bi65Jx5hYhlWrms883/hRUSjgKtJ9PFWl8wUVTJJsrOg0fm2ZM+YXpO9AvziRTEPj2MeibQjjlfsbTwKBgHc1ZBiUzMFzWM8IYh3mTC5MpiH7WiMEggZ6bIKn/mnnMoQvcFB8OgbnBQ+0kCxEkcAgxi5yrIk7e5aabbTA51gd1dlnF4fdijQXO4fWo99aob9eiQVzYn4/9GTWjNR9jy2SDnF9c2PTrP92njsOd1NuPh+KOG0nk4TMHhcBhmoZAoGAF4ZPy8ZZ5MsITIzS6zSCIUm7/gqo9lsNC+XAE8iv0+fOPpeSyu+8Mm89gxUNmr+6Zaxlk3dD/mvrkI0CV9j52RQ80yq7T7pOBlUMjDuOm680p7xqdB71u2ttvmeTkuJmczgshd+f9Y715c2qFt1nQNKkJjoWrN6BP8Q5E+Dh1jUCgYEApBuFGwmcSob6rIzinM4nJRyMLXqIy6nKCy8bBLkWCFPGztjIxhKBA/pErADhkLaLyM5reeMPNBbxruAFJ3mzfOneKf9lt0oHq2/jq0vJvYUj9tZjD2Sw6dhj0JqQhKIkhux1rU1xAAedY21JGTS+Be0dFfSv5uDrErPJiN+Qyt0=
-1
View File
@@ -1 +0,0 @@
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAneaLKDb6CzOdqTfFNeUJEV6RlcZNFmqDS1oDduKZ1t9BSADt26fKg9hVyc19ob8ONyyMLnVRG8Jnc3srVVgzbooC6hLCSbFwh4fTkSoxlR70tuNK+onX8Sdd/Sc9nd6EmRvGnB3R4LPx/zPfDvJc/WW9ih7Zvq8aYsLFdknYTLXzAyZkceKdCZJYCfE1dWQKaMqWxrUSlxzoq1UrOs34GmGnoJVDSEwILGWTWZPo/T6ee9hccmK8sZFGllZ9PUQA9O171iiXN3+zQRJQNi6bmV02CYdwV2iPzdObXYD3o+aPfT12gNlDIXaqO+/M6EKHlszr2zTFKmUTqhluVg8IXQIDAQAB
-1
View File
@@ -1 +0,0 @@
resources/
-32
View File
@@ -1,32 +0,0 @@
.PHONY: clean all run
all : resources/paper.jar resources/waterfall.jar resources/velocity.jar resources/sponge.jar resources/viaversion.jar
resources :
mkdir resources
resources/paper.jar : resources
wget -O resources/paper.jar https://papermc.io/api/v1/paper/1.16.2/182/download
resources/waterfall.jar : resources
wget -O resources/waterfall.jar https://papermc.io/ci/job/Waterfall/lastSuccessfulBuild/artifact/Waterfall-Proxy/bootstrap/target/Waterfall.jar
resources/velocity.jar : resources
wget -O resources/velocity.jar https://ci.velocitypowered.com/job/velocity-1.1.0/lastSuccessfulBuild/artifact/proxy/build/libs/velocity-proxy-1.1.0-SNAPSHOT-all.jar
resources/sponge.jar : resources
wget -O resources/sponge.jar https://repo.spongepowered.org/maven/org/spongepowered/spongevanilla/1.12.2-7.3.0/spongevanilla-1.12.2-7.3.0.jar
resources/viaversion.jar : resources
wget -O resources/viaversion.jar https://ci.viaversion.com/job/ViaVersion/459/artifact/jar/target/ViaVersion-3.1.1-SNAPSHOT.jar
clean :
rm -r resources/
run :
tmux new-session './p1/run.sh' \; \
split-window -h './p2/run.sh' \; \
split-window -h './s1/run.sh' \; \
split-window -h './s2/run.sh' \; \
select-layout even-horizontal
-29
View File
@@ -1,29 +0,0 @@
## Using this testbed
This testbed runs two proxies, labeled p1 and p2, as well as two servers behind it,
labeled s1 and s2. Ports for where everything is bound is listed below.
First, download required sources using the following command:
```
make
```
Then, each time you want to launch a session, run:
```
make run
```
FYI, this only works in Linux afaik because thats what I do my development on.
# Ports
| Server | Server Software | MC Port | Votifier Port |
| ------ | ---------------------- | ------- | ------------- |
| p1 | Waterfall/BungeeCord | 25577 | 8192 |
| p2 | Velocity | 25578 | 8193 |
| s1 | Paper (1.16.1) | 25565 | 8194 |
| s2 | SpongeVanilla (1.12.2) | 25566 | 8195 |
For compatibility with 1.16.1, s2 includes ViaVersion.
-4
View File
@@ -1,4 +0,0 @@
logs/
modules/
locations.yml
-52
View File
@@ -1,52 +0,0 @@
server_connect_timeout: 5000
listeners:
- query_port: 25577
motd: '&4its a bungee cord surbur'
tab_list: GLOBAL_PING
query_enabled: false
proxy_protocol: false
forced_hosts: {}
ping_passthrough: false
priorities:
- s1
bind_local_address: true
host: 0.0.0.0:25577
max_players: 1
tab_size: 60
force_default_server: false
remote_ping_cache: -1
network_compression_threshold: 256
permissions:
default:
- bungeecord.command.server
- bungeecord.command.list
admin:
- bungeecord.command.alert
- bungeecord.command.end
- bungeecord.command.ip
- bungeecord.command.reload
log_pings: true
connection_throttle_limit: 3
prevent_proxy_connections: true
timeout: 30000
player_limit: -1
ip_forward: true
groups:
md_5:
- admin
remote_ping_timeout: 5000
connection_throttle: 1
log_commands: false
stats: bf8a54ea-2917-4b9e-b463-2c05bd7f1ac6
online_mode: true
forge_support: true
disabled_commands: []
servers:
s1:
motd: SERVER 1
address: localhost:25565
restricted: false
s2:
motd: SERVER 2
address: localhost:25566
restricted: false
-8
View File
@@ -1,8 +0,0 @@
version: 2
modules:
- jenkins://cmd_alert
- jenkins://cmd_find
- jenkins://cmd_list
- jenkins://cmd_send
- jenkins://cmd_server
- jenkins://reconnect_yaml
-2
View File
@@ -1,2 +0,0 @@
rsa/
cached-votes.json
-74
View File
@@ -1,74 +0,0 @@
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
host: 0.0.0.0
# Port to listen for new votes on
port: 8192
# Whether or not to print debug messages. In a production system, this should be set to false.
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
debug: true
# All tokens, labeled by the serviceName of each server list.
tokens:
# Default token for all server lists, if another isn't supplied.
default: proxy1token
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
# option is currently not recommended as most voting sites only support the old protocol at present.
disable-v1-protocol: false
# Configuration section for all vote forwarding to NuVotifier
forwarding:
# Sets whether to set up a remote method for fowarding. Supported methods:
# - none - Does not set up a forwarding method.
# - pluginMessaging - Sets up plugin messaging.
# - proxy - Proxies votes to other NuVotifier servers from this server.
method: pluginMessaging
pluginMessaging:
channel: nuvotifier:votes
#Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
#to them. If excludedServers is not present, votes will be sent to all servers.
# If onlySendToJoinedServer is true and a player is on one of these servers, NuVotifier will process the vote as if the
# player was not on the network at all.
#excludedServers:
#- server1
#- server2
# If uncommented, turns excludedServers into an includedServers, where only servers on the list will be used.
#whitelist: true
# Sets which cache to use if there are no players on the server that the vote is being sent to. Supported caches:
# - none - Votes will simply be lost when no players are present on the server.
# - memory - Votes will be cached in memory when no players are present. When Bungee is restarted, the votes will be lost.
# - file - Votes will be saved to a local file every 3 minutes and on shutdown and reloaded when Bungee is restarted. This is suitable for
# most set ups.
cache: file
# Only sends the vote to the server in which the player is currently joined.
onlySendToJoinedServer: true
#Sets which server to send the vote in the event the player is not online when the vote is cast
#If you do not want the vote forwarded to a fallback, set this value to empty ('')
#ONLY USED IF onlySendToJoinedServer is true!!
joinedServerFallback: 's1'
# Defines how quickly to dump votes over a player's connection when offloading a cache in votes per second
dumpRate: 5
# Options for file caching.
file:
name: cached-votes.json
# days before a vote is considered 'dead' - removed from cache with a console warning
cacheTime: 10
# Options for memory caching.
memory:
# days before a vote is considered 'dead' and removed from memory. All votes are removed when the server restarts. -1 signifies no TTL
cacheTime: -1
# Specify servers to proxy votes for.
proxy:
Hub:
address: 127.0.0.1
port: 8194
token: server1token
Hub2:
address: 127.0.0.1
port: 8195
token: server2token
-1
View File
@@ -1 +0,0 @@
../../../universal/build/libs/nuvotifier.jar
-1
View File
@@ -1 +0,0 @@
../resources/waterfall.jar
-6
View File
@@ -1,6 +0,0 @@
#!/bin/sh
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
cd $DIR
java -jar proxy.jar
-9
View File
@@ -1,9 +0,0 @@
allow_empty_packets: false
throttling:
tab_complete: 1000
game_version: ''
disable_modern_tab_limiter: true
use_netty_dns_resolver: true
disable_entity_metadata_rewrite: false
log_initial_handler_connections: true
log_server_list_ping: false
-1
View File
@@ -1 +0,0 @@
logs/
-1
View File
@@ -1 +0,0 @@
../../../universal/build/libs/nuvotifier.jar
-2
View File
@@ -1,2 +0,0 @@
rsa/
cached-votes.json
-79
View File
@@ -1,79 +0,0 @@
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
host = "0.0.0.0"
# Port to listen for new votes on
port = 8193
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are
# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.
disable-v1-protocol = false
# All tokens, labeled by the serviceName of each server list.
[tokens]
# Default token for all server lists, if another isn't supplied.
default = "proxy2token"
# Configuration section for all vote forwarding to NuVotifier
[forwarding]
# Sets whether to set up a remote method for fowarding. Supported methods:
# - none - Does not set up a forwarding method.
# - pluginMessaging - Sets up plugin messaging.
# - proxy - Proxies votes to other NuVotifier servers from this server.
method = "pluginMessaging"
[forwarding.pluginMessaging]
channel = "nuvotifier:votes"
# Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
# to them. If excludedServers is not present, votes will be sent to all servers.
# If onlySendToJoinedServer is true and a player is on one of these servers, NuVotifier will process the vote as if the
# player was not on the network at all.
# excludedServers = ["server1", "server2"]
# If uncommented, turns excludedServers into an includedServers, where only servers on the list will be used.
# whitelist = true
# Sets which cache to use if there are no players on the server that the vote is being sent to. Supported caches:
# - none - Votes will simply be lost when no players are present on the server.
# - memory - Votes will be cached in memory when no players are present. When Bungee is restarted, the votes will be lost.
# - file - Votes will be saved to a local file every 3 minutes and on shutdown and reloaded when Bungee is restarted. This is suitable for
# most set ups.
cache = "file"
# Only sends the vote to the server in which the player is currently joined.
onlySendToJoinedServer = false
# Sets which server to send the vote in the event the player is not online when the vote is cast
# If you do not want the vote forwarded to a fallback, set this value to empty ('')
# ONLY USED IF onlySendToJoinedServer is true!!
joinedServerFallback = "s1"
# Defines how quickly to dump votes over a player's connection while offloading a cache in votes per second
dumpRate = 5
[forwarding.file-cache]
# Options for file caching.
name = "cached-votes.json"
# days before a vote is considered 'dead' - removed from cache with a console warning. -1 signifies no cache.
cacheTime = 10
[forwarding.memory-cache]
# Options for file caching.
name = "cached-votes.json"
# days before a vote is considered 'dead' - removed from cache with a console warning. -1 signifies no cache. Votes are
# also removed on restart
cacheTime = 10
[forwarding.proxy]
# Specify servers to proxy votes for.
[forwarding.proxy.Hub]
address = "127.0.0.1"
port = 8194
token = "server1token"
[forwarding.proxy.Hub2]
address = "127.0.0.1"
port = 8195
token = "server2token"
-1
View File
@@ -1 +0,0 @@
../resources/velocity.jar
-6
View File
@@ -1,6 +0,0 @@
#!/bin/sh
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
cd $DIR
java -jar proxy.jar
-135
View File
@@ -1,135 +0,0 @@
# Config version. Do not change this
config-version = "1.0"
# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.
bind = "0.0.0.0:25578"
# What should be the MOTD? This gets displayed when the player adds your server to
# their server list. Legacy color codes and JSON are accepted.
motd = "&3doot doot imma velocity serbur"
# What should we display for the maximum number of players? (Velocity does not support a cap
# on the number of players online.)
show-max-players = 500
# Should we authenticate players with Mojang? By default, this is on.
online-mode = true
# If client's ISP/AS sent from this proxy is different from the one from Mojang's
# authentication server, the player is kicked. This disallows some VPN and proxy
# connections but is a weak form of protection.
prevent-client-proxy-connections = false
# Should we forward IP addresses and other data to backend servers?
# Available options:
# - "none": No forwarding will be done. All players will appear to be connecting
# from the proxy and will have offline-mode UUIDs.
# - "legacy": Forward player IPs and UUIDs in a BungeeCord-compatible format. Use this
# if you run servers using Minecraft 1.12 or lower.
# - "bungeeguard": Forward player IPs and UUIDs in a format supported by the BungeeGuard
# plugin. Use this if you run servers using Minecraft 1.12 or lower, and are
# unable to implement network level firewalling (on a shared host).
# - "modern": Forward player IPs and UUIDs as part of the login process using
# Velocity's native forwarding. Only applicable for Minecraft 1.13 or higher.
player-info-forwarding-mode = "LEGACY"
# If you are using modern or BungeeGuard IP forwarding, configure an unique secret here.
forwarding-secret = "not at all secret lolololol"
# Announce whether or not your server supports Forge. If you run a modded server, we
# suggest turning this on.
#
# If your network runs one modpack consistently, consider using ping-passthrough = "mods"
# instead for a nicer display in the server list.
announce-forge = false
# If enabled (default is false) and the proxy is in online mode, Velocity will kick
# any existing player who is online if a duplicate connection attempt is made.
kick-existing-players = false
# Should Velocity pass server list ping requests to a backend server?
# Available options:
# - "disabled": No pass-through will be done. The velocity.toml and server-icon.png
# will determine the initial server list ping response.
# - "mods": Passes only the mod list from your backend server into the response.
# The first server in your try list (or forced host) with a mod list will be
# used. If no backend servers can be contacted, Velocity won't display any
# mod information.
# - "description": Uses the description and mod list from the backend server. The first
# server in the try (or forced host) list that responds is used for the
# description and mod list.
# - "all": Uses the backend server's response as the proxy response. The Velocity
# configuration is used if no servers could be contacted.
ping-passthrough = "DISABLED"
[servers]
# Configure your servers here. Each key represents the server's name, and the value
# represents the IP address of the server to connect to.
s1 = "127.0.0.1:25565"
s2 = "127.0.0.1:25566"
# In what order we should try servers when a player logs in or is kicked from aserver.
try = [
"s1"
]
[forced-hosts]
# Configure your forced hosts here.
[advanced]
# How large a Minecraft packet has to be before we compress it. Setting this to zero will
# compress all packets, and setting it to -1 will disable compression entirely.
compression-threshold = 256
# How much compression should be done (from 0-9). The default is -1, which uses the
# default level of 6.
compression-level = -1
# How fast (in milliseconds) are clients allowed to connect after the last connection? By
# default, this is three seconds. Disable this by setting this to 0.
login-ratelimit = 3000
# Specify a custom timeout for connection timeouts here. The default is five seconds.
connection-timeout = 5000
# Specify a read timeout for connections here. The default is 30 seconds.
read-timeout = 30000
# Enables compatibility with HAProxy.
proxy-protocol = false
# Enables TCP fast open support on the proxy. Requires the proxy to run on Linux.
tcp-fast-open = false
# Enables BungeeCord plugin messaging channel support on Velocity.
bungee-plugin-message-channel = true
# Shows ping requests to the proxy from clients.
show-ping-requests = false
[query]
# Whether to enable responding to GameSpy 4 query responses or not.
enabled = false
# If query is enabled, on what port should the query protocol listen on?
port = 25577
# This is the map name that is reported to the query services.
map = "Velocity"
# Whether plugins should be shown in query response by default or not
show-plugins = false
[metrics]
# Whether metrics will be reported to bStats (https://bstats.org).
# bStats collects some basic information, like how many people use Velocity and their
# player count. We recommend keeping bStats enabled, but if you're not comfortable with
# this, you can turn this setting off. There is no performance penalty associated with
# having metrics enabled, and data sent to bStats can't identify your server.
enabled = true
# A unique, anonymous ID to identify this proxy with.
id = "729fe923-b383-4e5b-b456-5adf554bfe69"
log-failure = false
-10
View File
@@ -1,10 +0,0 @@
#!/bin/sh
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
cd $DIR
echo <<WARNING
Hey! Just so you know, I am using the IReallyKnowWhatIAmDoingISwear flag, as
well as overriding the eula check. If you don't agree to the Mojang EULA
thingie, then uh, smash your computer or something because you totally agreed
to it so just fyi
WARNING
java -DIReallyKnowWhatIAmDoingISwear -Dcom.mojang.eula.agree=true -jar paper.jar nogui
-19
View File
@@ -1,19 +0,0 @@
banned-ips.json
banned-players.json
cache/
eula.txt
logs/
permissions.yml
ops.json
usercache.json
version_history.json
whitelist.json
world/
world_nether/
world_the_end/
-42
View File
@@ -1,42 +0,0 @@
# This is the main configuration file for Bukkit.
# As you can see, there's actually not that much to configure without any plugins.
# For a reference for any variable inside this file, check out the Bukkit Wiki at
# https://www.spigotmc.org/go/bukkit-yml
#
# If you need help on this file, feel free to join us on irc or leave a message
# on the forums asking for advice.
#
# IRC: #spigot @ irc.spi.gt
# (If this means nothing to you, just go to https://www.spigotmc.org/go/irc )
# Forums: https://www.spigotmc.org/
# Bug tracker: https://www.spigotmc.org/go/bugs
settings:
minimum-api: none
allow-end: true
warn-on-overload: true
permissions-file: permissions.yml
update-folder: update
plugin-profiling: false
connection-throttle: 4000
query-plugins: true
deprecated-verbose: default
shutdown-message: Server closed
spawn-limits:
water-ambient: 20
monsters: 70
animals: 10
water-animals: 15
ambient: 15
chunk-gc:
period-in-ticks: 600
load-threshold: 0
ticks-per:
water-ambient-spawns: 1
water-spawns: 1
ambient-spawns: 1
animal-spawns: 400
monster-spawns: 1
autosave: 6000
aliases: now-in-commands.yml
-17
View File
@@ -1,17 +0,0 @@
# This is the commands configuration file for Bukkit.
# For documentation on how to make use of this file, check out the Bukkit Wiki at
# https://www.spigotmc.org/go/commands-yml
#
# If you need help on this file, feel free to join us on irc or leave a message
# on the forums asking for advice.
#
# IRC: #spigot @ irc.spi.gt
# (If this means nothing to you, just go to https://www.spigotmc.org/go/irc )
# Forums: https://www.spigotmc.org/
# Bug tracker: https://www.spigotmc.org/go/bugs
command-block-overrides: []
ignore-vanilla-permissions: false
aliases:
icanhasbukkit:
- version $1-
-56
View File
@@ -1,56 +0,0 @@
# This is the help configuration file for Bukkit.
#
# By default you do not need to modify this file. Help topics for all plugin commands are automatically provided by
# or extracted from your installed plugins. You only need to modify this file if you wish to add new help pages to
# your server or override the help pages of existing plugin commands.
#
# This file is divided up into the following parts:
# -- general-topics: lists admin defined help topics
# -- index-topics: lists admin defined index topics
# -- amend-topics: lists topic amendments to apply to existing help topics
# -- ignore-plugins: lists any plugins that should be excluded from help
#
# Examples are given below. When amending command topic, the string <text> will be replaced with the existing value
# in the help topic. Color codes can be used in topic text. The color code character is & followed by 0-F.
# ================================================================
#
# Set this to true to list the individual command help topics in the master help.
# command-topics-in-master-index: true
#
# Each general topic will show up as a separate topic in the help index along with all the plugin command topics.
# general-topics:
# Rules:
# shortText: Rules of the server
# fullText: |
# &61. Be kind to your fellow players.
# &B2. No griefing.
# &D3. No swearing.
# permission: topics.rules
#
# Each index topic will show up as a separate sub-index in the help index along with all the plugin command topics.
# To override the default help index (displayed when the user executes /help), name the index topic "Default".
# index-topics:
# Ban Commands:
# shortText: Player banning commands
# preamble: Moderator - do not abuse these commands
# permission: op
# commands:
# - /ban
# - /ban-ip
# - /banlist
#
# Topic amendments are used to change the content of automatically generated plugin command topics.
# amended-topics:
# /stop:
# shortText: Stops the server cold....in its tracks!
# fullText: <text> - This kills the server.
# permission: you.dont.have
#
# Any plugin in the ignored plugins list will be excluded from help. The name must match the name displayed by
# the /plugins command. Ignore "Bukkit" to remove the standard bukkit commands from the index. Ignore "All"
# to completely disable automatic help topic generation.
# ignore-plugins:
# - PluginNameOne
# - PluginNameTwo
# - PluginNameThree
-1
View File
@@ -1 +0,0 @@
../resources/paper.jar
-224
View File
@@ -1,224 +0,0 @@
# This is the main configuration file for Paper.
# As you can see, there's tons to configure. Some options may impact gameplay, so use
# with caution, and make sure you know what each option does before configuring.
#
# If you need help with the configuration or have any questions related to Paper,
# join us in our Discord or IRC channel.
#
# Discord: https://paperdiscord.emc.gs
# IRC: #paper @ irc.spi.gt ( http://irc.spi.gt/iris/?channels=paper )
# Website: https://papermc.io/
# Docs: https://paper.readthedocs.org/
verbose: false
config-version: 20
settings:
max-joins-per-tick: 3
console-has-all-permissions: false
chunk-tasks-per-tick: 1000
load-permissions-yml-before-plugins: true
bungee-online-mode: true
use-versioned-world: false
velocity-support:
enabled: false
online-mode: false
secret: ''
queue-light-updates-max-loss: 10
suggest-player-names-when-null-tab-completions: true
watchdog:
early-warning-every: 5000
early-warning-delay: 10000
spam-limiter:
tab-spam-increment: 1
tab-spam-limit: 500
save-player-data: true
sleep-between-chunk-saves: false
region-file-cache-size: 256
save-empty-scoreboard-teams: false
incoming-packet-spam-threshold: 300
book-size:
page-max: 2560
total-multiplier: 0.98
enable-player-collisions: true
player-auto-save-rate: -1
max-player-auto-save-per-tick: -1
async-chunks:
enable: true
threads: -1
use-alternative-luck-formula: false
unsupported-settings:
allow-headless-pistons: false
allow-permanent-block-break-exploits: false
allow-piston-duplication: false
allow-permanent-block-break-exploits-readme: This setting controls if players
should be able to break bedrock, end portals and other intended to be permanent
blocks.
allow-piston-duplication-readme: This setting controls if player should be able
to use TNT duplication, but this also allows duplicating carpet, rails and potentially
other items
allow-headless-pistons-readme: This setting controls if players should be able
to create headless pistons.
messages:
no-permission: '&cI''m sorry, but you do not have permission to perform this command.
Please contact the server administrators if you believe that this is in error.'
kick:
authentication-servers-down: ''
flying-player: Flying is not enabled on this server
flying-vehicle: Flying is not enabled on this server
connection-throttle: Connection throttled! Please wait before reconnecting.
timings:
server-name: Unknown Server
enabled: true
verbose: true
server-name-privacy: false
hidden-config-entries:
- database
- settings.bungeecord-addresses
history-interval: 300
history-length: 3600
world-settings:
default:
zombies-target-turtle-eggs: true
per-player-mob-spawns: false
phantoms-do-not-spawn-on-creative-players: true
phantoms-only-attack-insomniacs: true
light-queue-size: 20
count-all-mobs-for-spawning: false
seed-based-feature-search: true
nether-ceiling-void-damage-height: 0
fix-zero-tick-instant-grow-farms: true
portal-create-radius: 16
iron-golems-can-spawn-in-air: false
entities-target-with-follow-range: false
zombie-villager-infection-chance: -1.0
portal-search-radius: 128
game-mechanics:
disable-pillager-patrols: false
nerf-pigmen-from-nether-portals: false
disable-relative-projectile-velocity: false
villages-load-chunks: false
scan-for-legacy-ender-dragon: true
disable-chest-cat-detection: false
disable-end-credits: false
disable-player-crits: false
disable-sprint-interruption-on-attack: false
shield-blocking-delay: 5
disable-unloaded-chunk-enderpearl-exploit: true
pillager-patrols:
spawn-chance: 0.2
spawn-delay:
per-player: false
ticks: 12000
start:
per-player: false
day: 5
queue-light-updates: false
optimize-explosions: false
disable-teleportation-suffocation-check: false
fixed-chunk-inhabited-time: -1
use-vanilla-world-scoreboard-name-coloring: false
delay-chunk-unloads-by: 10s
max-auto-save-chunks-per-tick: 24
save-queue-limit-for-auto-save: 50
enable-treasure-maps: true
treasure-maps-return-already-discovered: false
experience-merge-max-value: -1
max-chunk-sends-per-tick: 81
max-chunk-gens-per-tick: 10
prevent-moving-into-unloaded-chunks: false
max-growth-height:
cactus: 3
reeds: 3
fishing-time-range:
MinimumTicks: 100
MaximumTicks: 600
despawn-ranges:
soft: 32
hard: 128
falling-block-height-nerf: 0
tnt-entity-height-nerf: 0
lightning-strike-distance-limit:
sound: -1
impact-sound: -1
flash: -1
frosted-ice:
enabled: true
delay:
min: 20
max: 40
lootables:
auto-replenish: false
restrict-player-reloot: true
reset-seed-on-fill: true
max-refills: -1
refresh-min: 12h
refresh-max: 2d
filter-nbt-data-from-spawn-eggs-and-related: true
max-entity-collisions: 8
disable-creeper-lingering-effect: false
hopper:
cooldown-when-full: true
disable-move-event: false
duplicate-uuid-resolver: saferegen
duplicate-uuid-saferegen-delete-range: 32
remove-corrupt-tile-entities: false
keep-spawn-loaded: true
anti-xray:
enabled: false
engine-mode: 1
chunk-edge-mode: 1
max-chunk-section-index: 3
update-radius: 2
hidden-blocks:
- gold_ore
- iron_ore
- coal_ore
- lapis_ore
- mossy_cobblestone
- obsidian
- chest
- diamond_ore
- redstone_ore
- clay
- emerald_ore
- ender_chest
replacement-blocks:
- stone
- oak_planks
skip-entity-ticking-in-chunks-scheduled-for-unload: true
armor-stands-do-collision-entity-lookups: true
keep-spawn-loaded-range: 8
auto-save-interval: -1
disable-thunder: false
skeleton-horse-thunder-spawn-chance: 0.01
disable-ice-and-snow: false
fire-physics-event-for-redstone: false
grass-spread-tick-rate: 1
water-over-lava-flow-speed: 5
bed-search-radius: 1
use-faster-eigencraft-redstone: false
nether-ceiling-void-damage: false
allow-non-player-entities-on-scoreboards: false
container-update-tick-rate: 1
prevent-tnt-from-moving-in-water: false
non-player-arrow-despawn-rate: -1
creative-arrow-despawn-rate: -1
parrots-are-unaffected-by-player-movement: false
disable-explosion-knockback: false
armor-stands-tick: true
spawner-nerfed-mobs-should-jump: false
allow-leashing-undead-horse: false
baby-zombie-movement-speed: 0.5
all-chunks-are-slime-chunks: false
mob-spawner-tick-rate: 1
squid-spawn-height:
maximum: 0.0
alt-item-despawn-rate:
enabled: false
items:
COBBLESTONE: 300
viewdistances:
no-tick-view-distance: -1
generator-settings:
flat-bedrock: false
baby-zombie-movement-modifier: 0.5
-1
View File
@@ -1 +0,0 @@
rsa/
-29
View File
@@ -1,29 +0,0 @@
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
host: 0.0.0.0
# Port to listen for new votes on
port: 8194
# Whether or not to print debug messages. In a production system, this should be set to false.
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
debug: true
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are
# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.
disable-v1-protocol: false
# All tokens, labeled by the serviceName of each server list.
tokens:
# Default token for all server lists, if another isn't supplied.
default: server1token
# Configuration section for all vote forwarding to NuVotifier
forwarding:
# Sets whether to set up a remote method for fowarding. Supported methods:
# - none - Does not set up a forwarding method.
# - pluginMessaging - Sets up plugin messaging
method: pluginMessaging
pluginMessaging:
channel: nuvotifier:votes
-7
View File
@@ -1,7 +0,0 @@
# bStats collects some data for plugin authors like how many servers are using their plugins.
# To honor their work, you should not disable it.
# This has nearly no effect on the server performance!
# Check out https://bStats.org/ to learn more :)
enabled: true
serverUuid: 44d2e780-6de3-4b18-a28c-745f58727b99
logFailedRequests: false
-1
View File
@@ -1 +0,0 @@
../../../universal/build/libs/nuvotifier.jar
-1
View File
@@ -1 +0,0 @@
../s-run.sh
-52
View File
@@ -1,52 +0,0 @@
#Minecraft server properties
#Thu Sep 03 15:49:51 EDT 2020
spawn-protection=16
max-tick-time=60000
query.port=25565
generator-settings=
sync-chunk-writes=true
force-gamemode=false
allow-nether=true
enforce-whitelist=false
gamemode=survival
broadcast-console-to-ops=true
enable-query=false
player-idle-timeout=0
difficulty=easy
spawn-monsters=true
broadcast-rcon-to-ops=true
op-permission-level=4
pvp=true
entity-broadcast-range-percentage=100
snooper-enabled=true
level-type=default
hardcore=false
enable-status=true
enable-command-block=false
max-players=20
network-compression-threshold=256
resource-pack-sha1=
max-world-size=29999984
function-permission-level=2
rcon.port=25575
server-port=25565
debug=false
server-ip=
spawn-npcs=true
allow-flight=false
level-name=world
view-distance=10
resource-pack=
spawn-animals=true
white-list=false
rcon.password=
generate-structures=true
max-build-height=256
online-mode=false
level-seed=
prevent-proxy-connections=false
use-native-transport=true
enable-jmx-monitoring=false
rate-limit=0
enable-rcon=false
motd=A Minecraft Server
-163
View File
@@ -1,163 +0,0 @@
# This is the main configuration file for Spigot.
# As you can see, there's tons to configure. Some options may impact gameplay, so use
# with caution, and make sure you know what each option does before configuring.
# For a reference for any variable inside this file, check out the Spigot wiki at
# http://www.spigotmc.org/wiki/spigot-configuration/
#
# If you need help with the configuration or have any questions related to Spigot,
# join us at the IRC or drop by our forums and leave a post.
#
# IRC: #spigot @ irc.spi.gt ( http://www.spigotmc.org/pages/irc/ )
# Forums: http://www.spigotmc.org/
config-version: 12
settings:
log-villager-deaths: true
debug: false
save-user-cache-on-stop-only: false
sample-count: 12
user-cache-size: 1000
moved-wrongly-threshold: 0.0625
moved-too-quickly-multiplier: 10.0
bungeecord: true
late-bind: false
player-shuffle: 0
timeout-time: 60
restart-on-crash: true
restart-script: ./start.sh
netty-threads: 4
attribute:
maxHealth:
max: 2048.0
movementSpeed:
max: 2048.0
attackDamage:
max: 2048.0
messages:
whitelist: You are not whitelisted on this server!
unknown-command: Unknown command. Type "/help" for help.
server-full: The server is full!
outdated-client: Outdated client! Please use {0}
outdated-server: Outdated server! I'm still on {0}
restart: Server is restarting
stats:
disable-saving: false
forced-stats: {}
commands:
tab-complete: 0
send-namespaced: true
log: true
spam-exclusions:
- /skill
silent-commandblock-console: false
replace-commands:
- setblock
- summon
- testforblock
- tellraw
advancements:
disable-saving: false
disabled:
- minecraft:story/disabled
world-settings:
default:
seed-bastion: 30084232
seed-fortress: 30084232
seed-mansion: 10387319
seed-fossil: 14357921
seed-portal: 34222645
seed-outpost: 165745296
seed-endcity: 10387313
view-distance: default
trident-despawn-rate: 1200
end-portal-sound-radius: 0
verbose: true
merge-radius:
exp: 3.0
item: 2.5
random-light-updates: false
dragon-death-sound-radius: 0
seed-village: 10387312
seed-desert: 14357617
seed-igloo: 14357618
seed-jungle: 14357619
seed-swamp: 14357620
seed-monument: 10387313
seed-shipwreck: 165745295
seed-ocean: 14357621
seed-slime: 987234911
hunger:
jump-walk-exhaustion: 0.05
jump-sprint-exhaustion: 0.2
combat-exhaustion: 0.1
regen-exhaustion: 6.0
swim-multiplier: 0.01
sprint-multiplier: 0.1
other-multiplier: 0.0
max-tnt-per-tick: 100
max-tick-time:
tile: 50
entity: 50
squid-spawn-range:
min: 45.0
mob-spawn-range: 8
growth:
beetroot-modifier: 100
carrot-modifier: 100
potato-modifier: 100
bamboo-modifier: 100
sweetberry-modifier: 100
kelp-modifier: 100
cactus-modifier: 100
cane-modifier: 100
melon-modifier: 100
mushroom-modifier: 100
pumpkin-modifier: 100
sapling-modifier: 100
wheat-modifier: 100
netherwart-modifier: 100
vine-modifier: 100
cocoa-modifier: 100
entity-activation-range:
raiders: 48
villagers: 32
flying-monsters: 32
villagers-work-immunity-after: 100
villagers-work-immunity-for: 20
villagers-active-for-panic: true
animals: 32
monsters: 32
misc: 16
water: 16
tick-inactive-villagers: true
wake-up-inactive:
animals-max-per-tick: 4
animals-every: 1200
animals-for: 100
monsters-max-per-tick: 8
monsters-every: 400
monsters-for: 100
villagers-max-per-tick: 4
villagers-every: 600
villagers-for: 100
flying-monsters-max-per-tick: 8
flying-monsters-every: 200
flying-monsters-for: 100
entity-tracking-range:
players: 48
animals: 48
monsters: 48
misc: 32
other: 64
ticks-per:
hopper-transfer: 8
hopper-check: 1
hopper-amount: 1
wither-spawn-sound-radius: 0
enable-zombie-pigmen-portal-spawns: true
item-despawn-rate: 6000
arrow-despawn-rate: 1200
hanging-tick-frequency: 100
zombie-aggressive-towards-villager: true
nerf-spawner-mobs: false
max-entity-collisions: 8
Loaded 100 of 181 files, more files were not shown because too many files have changed in this diff. Show more