Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c6dc99bcc | ||
|
|
f5776dd1cb | ||
|
|
7134c12826 | ||
|
|
8cc93daf70 | ||
|
|
98dfc345e9 | ||
|
|
590b7618f3 | ||
|
|
4cd2ce55fc | ||
|
|
9fe98a92a9 | ||
|
|
c72972c286 | ||
|
|
92a122eaf8 | ||
|
|
67117b38e3 | ||
|
|
9255062aff | ||
|
|
fe45e48447 | ||
|
|
0391e9e4bf | ||
|
|
59fa4857e3 | ||
|
|
4d663737c1 | ||
|
|
8380ffce9a | ||
|
|
b9e648bba8 | ||
|
|
c34551e630 | ||
|
|
d931cc2464 | ||
|
|
07f81eb781 | ||
|
|
c0ef61c819 | ||
|
|
09bbfa8629 | ||
|
|
aef038ef3a | ||
|
|
ac3827625d | ||
|
|
acc092f955 | ||
|
|
b494e1dc9d | ||
|
|
27a368bd08 | ||
|
|
3c5b533a44 | ||
|
|
f569c725d0 | ||
|
|
a09bc5a131 | ||
|
|
c4e4f6e416 | ||
|
|
6ed181e835 | ||
|
|
3ac45554ac | ||
|
|
d27f6c5cea | ||
|
|
4d1f7016f6 | ||
|
|
2673fc2d4d | ||
|
|
60c787c7f2 | ||
|
|
713cf90c56 | ||
|
|
eba7ab9c86 | ||
|
|
d72977325c | ||
|
|
c3e9a5bac9 | ||
|
|
161617d636 | ||
|
|
cf95fd1f28 | ||
|
|
8f1bf5b4b9 | ||
|
|
ee9d3cae42 | ||
|
|
e4f910e576 | ||
|
|
77cf6bf457 | ||
|
|
8a72c4d9c7 | ||
|
|
39ae4ef860 | ||
|
|
d2d12a4a86 | ||
|
|
5644078997 | ||
|
|
bf5822282e | ||
|
|
55e883759f | ||
|
|
ba6a118058 | ||
|
|
93c0d53f2d | ||
|
|
8d4bdf69b3 | ||
|
|
700be0a04e | ||
|
|
bf7c78de46 | ||
|
|
c0ea57176c | ||
|
|
65bf833fa7 | ||
|
|
03beac9ae0 | ||
|
|
4277dfd74d | ||
|
|
28b4d8dcd0 | ||
|
|
733204b6b9 | ||
|
|
52956ce210 | ||
|
|
2de64da511 | ||
|
|
0ea6867d91 | ||
|
|
190bfaa4a9 | ||
|
|
b43dda37b2 | ||
|
|
4dfb157166 | ||
|
|
d8c2e072b1 | ||
|
|
359b616a41 | ||
|
|
9342f75317 | ||
|
|
f17bb4e414 | ||
|
|
5f347e538b | ||
|
|
b4fb8c69a9 | ||
|
|
95ebcf22a8 | ||
|
|
302da3c907 |
No files matched your search
+16
@@ -0,0 +1,16 @@
|
||||
pipeline:
|
||||
build:
|
||||
image: cogniteev/oracle-java
|
||||
commands: ./gradlew build test --no-daemon
|
||||
publish:
|
||||
image: cogniteev/oracle-java
|
||||
commands:
|
||||
- ./gradlew publishversionedPublicationToParallelBlockRepository
|
||||
- ./gradlew publishLatestToParallelBlockRaw
|
||||
environment:
|
||||
- PUBLISH=yes
|
||||
- LATEST_BUILD=yes
|
||||
secrets: [SNAPSHOT_REPO, RELEASE_REPO, REPO_USERNAME, REPO_PASSWORD, RAW_UPLOAD_PATH]
|
||||
when:
|
||||
branch: master
|
||||
event: push
|
||||
+7
-2
@@ -11,13 +11,14 @@
|
||||
/build.xml
|
||||
|
||||
# maven
|
||||
/target
|
||||
/*/target/
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
# vim
|
||||
.*.sw[a-p]
|
||||
|
||||
# various other potential build files
|
||||
/build
|
||||
build/
|
||||
/bin
|
||||
/dist
|
||||
/manifest.mf
|
||||
@@ -42,3 +43,7 @@ debug.log
|
||||
# Doxygen
|
||||
/docs/html
|
||||
debug.txt
|
||||
|
||||
# gradle stuff
|
||||
.gradle/
|
||||
.gradletasknamecache
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
sudo: false
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: j8y4qeDOXAYjmf6WU6EbpUA6AKTz26yJ4zNb6oAuB4h9Y3PpBRjFjCIBL6jJQnMxZMLjdgk72/3uqS7+//jJ8/iiY/OJhO2L+2us8TJDcQhH5EI1USe/lcZKf60iJjmd9Q9pjh1eHNYhxr6kyNLnpa6fw5b/hIBT6CvNZAJV1qbhRpHwtzbgdlMFaHGnpX192vJNw7E3uuJuSwue5XNK2ybDp81oTrM0Q16JCq/UsQhGq4Ba3JffOhdBhpfg4EI2jJfe3vN8CFqojCYKNSp7eL6DPFPaV4x8Ly8cRIOZ5Zb60m5SyWAcagPKJuUq3ZOTHfeVTjkI9ymZMHe1pla2Mq6eitIBwZIGnonjyMxcTdkR9EJgq9jo+1PzdStR/NlGLakBiwPzk8Ov0DZ+iOwpYUkISUkMU0UgKWwoFoRu18CHtp+ALEsoYZhBQGlcShq+4EGv9VMNBYk9vN3KXNRMVGKqXd6O3T6shvSQgoUn/3mvksOTAuhS5J4U3woHecDrXwl/RfP2aqiHhX/S7p7yY4hVcHu8kiSYpNQMbay+rNd6HxFcRe/U9HOUt6kW8/yo1ROyCxs3ze+N6LtGDHspgzkFaay6I6wMHYNDnzzRP4MC3Zc73zxViXol6dGBk4Tawsm16VaI6b1uyWLBGKvuU23azkmMTHUmGFXl2/BTaYw=
|
||||
file: universal/target/nuvotifier-universal-2.3.4.jar
|
||||
on:
|
||||
tags: true
|
||||
@@ -0,0 +1,35 @@
|
||||
# NuVotifier
|
||||
|
||||
NuVotifier is a Bukkit plugin whose purpose is to be notified (aka *votified*)
|
||||
when a vote is made on a Minecraft server top list for the server. NuVotifier
|
||||
creates a *lightweight* server that waits for connections by Minecraft server
|
||||
lists and uses a simple protocol to get the required information. NuVotifier is
|
||||
*secure*, and makes sure that all vote notifications are delivered by authentic
|
||||
top lists.
|
||||
|
||||
## Why fork Votifier?
|
||||
|
||||
Votifier has lagged in activity, has severe security flaws in its protocol, and
|
||||
is vulnerable to denial of service attacks.
|
||||
|
||||
## Is NuVotifier a drop-in replacement for Votifier?
|
||||
|
||||
Yes! Except for some minor non-breaking changes (look into the [technical qa
|
||||
docs](docs/technical_qa.md) for more info), NuVotifier is 100% compatible with
|
||||
the old Votifier with many more features.
|
||||
|
||||
# Documentation / Configuration Guide
|
||||
|
||||
All documentation is located under the [docs](docs/) directory. A server admin
|
||||
configuration guide is available [here](docs/usage.md).
|
||||
|
||||
# Support
|
||||
|
||||
Feel free to join the MOSS Discord server using the below link:
|
||||
|
||||
[](https://discord.gg/anUtuAC)
|
||||
|
||||
# License
|
||||
|
||||
NuVotifier is GNU GPLv3 licensed. This project's license can be viewed
|
||||
[here](LICENSE).
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
allprojects {
|
||||
group 'com.vexsoftware'
|
||||
version '2.5.0'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://repo.parallelblock.com/repository/maven-public/'
|
||||
}
|
||||
maven {
|
||||
url 'https://hub.spigotmc.org/nexus/content/groups/public/'
|
||||
}
|
||||
maven {
|
||||
url "https://oss.sonatype.org/content/groups/public/"
|
||||
}
|
||||
maven {
|
||||
url "http://repo.spongepowered.org/maven"
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
filter(ReplaceTokens, tokens: ["app.version": this.project.version])
|
||||
}
|
||||
}
|
||||
|
||||
if (System.env.PUBLISH) {
|
||||
apply from: 'publish.gradle'
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencies {
|
||||
compileOnly group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.4</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-bukkit</artifactId>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>maven-replacer-plugin</artifactId>
|
||||
<version>1.3.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>replace</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<file>target/classes/plugin.yml</file>
|
||||
<replacements>
|
||||
<replacement>
|
||||
<token>maven-version-number</token>
|
||||
<value>${project.version}</value>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-common</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.8.8-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,57 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,35 +21,31 @@ package com.vexsoftware.votifier;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.vexsoftware.votifier.forwarding.BukkitPluginMessagingForwardingSink;
|
||||
import com.vexsoftware.votifier.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.forwarding.ForwardingVoteSink;
|
||||
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.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
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.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
@@ -76,14 +72,9 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
* The server bootstrap.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
@@ -101,22 +92,18 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
private Map<String, Key> tokens = new HashMap<>();
|
||||
|
||||
private ForwardingVoteSink forwardingMethod;
|
||||
private VotifierScheduler scheduler;
|
||||
private LoggingAdapter pluginLogger;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
NuVotifierBukkit.instance = this;
|
||||
|
||||
// Set the plugin version.
|
||||
version = getDescription().getVersion();
|
||||
|
||||
private boolean loadAndBind() {
|
||||
scheduler = new BukkitScheduler(this);
|
||||
pluginLogger = new JavaUtilLogger(getLogger());
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
// Handle configuration.
|
||||
File config = new File(getDataFolder() + "/config.yml");
|
||||
YamlConfiguration cfg;
|
||||
File rsaDirectory = new File(getDataFolder() + "/rsa");
|
||||
File config = new File(getDataFolder(), "config.yml");
|
||||
|
||||
/*
|
||||
* Use IP address from server.properties as a default for
|
||||
@@ -143,7 +130,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
|
||||
Files.write(cfgStr, config, StandardCharsets.UTF_8);
|
||||
Files.asCharSink(config, StandardCharsets.UTF_8).write(cfgStr);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
@@ -161,11 +148,13 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "Error creating configuration file", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
YamlConfiguration cfg;
|
||||
File rsaDirectory = new File(getDataFolder(), "rsa");
|
||||
|
||||
// Load configuration.
|
||||
cfg = YamlConfiguration.loadConfiguration(config);
|
||||
|
||||
@@ -184,51 +173,46 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error reading configuration file or RSA tokens", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
debug = cfg.getBoolean("debug", false);
|
||||
|
||||
boolean setUpPort = cfg.getBoolean("enableExternal", true); //Always default to running the external port
|
||||
// Load Votifier tokens.
|
||||
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
|
||||
|
||||
if (setUpPort) {
|
||||
// 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);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
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("------------------------------------------------------------------------------");
|
||||
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("DEBUG mode enabled!");
|
||||
|
||||
// Initialize the receiver.
|
||||
final String host = cfg.getString("host", hostAddr);
|
||||
final int port = cfg.getInt("port", 8192);
|
||||
if (debug)
|
||||
getLogger().info("DEBUG mode enabled!");
|
||||
if (port >= 0) {
|
||||
final boolean disablev1 = cfg.getBoolean("disable-v1-protocol");
|
||||
if (disablev1) {
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
@@ -237,39 +221,14 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
serverGroup = new NioEventLoopGroup(1);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(NuVotifierBukkit.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(NuVotifierBukkit.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
this.bootstrap = new VotifierServerBootstrap(host, port, this, disablev1);
|
||||
this.bootstrap.start(error -> {});
|
||||
} else {
|
||||
getLogger().info("You have enableExternal set to false in your config.yml. NuVotifier will NOT listen to votes coming in from an external voting list.");
|
||||
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");
|
||||
@@ -289,22 +248,70 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
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() {
|
||||
NuVotifierBukkit.instance = this;
|
||||
|
||||
// Set the plugin version.
|
||||
version = getDescription().getVersion();
|
||||
|
||||
if (!loadAndBind()) {
|
||||
gracefulExit();
|
||||
setEnabled(false); // safer to just bomb out
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Shut down the network handlers.
|
||||
if (serverGroup != null) {
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
}
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
}
|
||||
halt();
|
||||
getLogger().info("Votifier disabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender instanceof ConsoleCommandSender) {
|
||||
reload();
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "For security and stability, only console may run this command!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void 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.");
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void gracefulExit() {
|
||||
getLogger().log(Level.SEVERE, "Votifier did not initialize properly!");
|
||||
}
|
||||
@@ -318,13 +325,14 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version.
|
||||
*
|
||||
* @return The version
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
@Override
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return pluginLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
@@ -342,7 +350,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
@@ -350,19 +358,19 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
}
|
||||
}
|
||||
Bukkit.getScheduler().runTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bukkit.getPluginManager().callEvent(new VotifierEvent(vote));
|
||||
}
|
||||
});
|
||||
Bukkit.getScheduler().runTask(this, () -> Bukkit.getPluginManager().callEvent(new VotifierEvent(vote)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, Throwable throwable) {
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
if (debug) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
if (alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
|
||||
}
|
||||
}
|
||||
@@ -372,11 +380,6 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
if (debug) {
|
||||
getLogger().info("Got a forwarded vote -> " + v);
|
||||
}
|
||||
Bukkit.getScheduler().runTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bukkit.getPluginManager().callEvent(new VotifierEvent(v));
|
||||
}
|
||||
});
|
||||
Bukkit.getScheduler().runTask(this, () -> Bukkit.getPluginManager().callEvent(new VotifierEvent(v)));
|
||||
}
|
||||
}
|
||||
+5
-10
@@ -1,33 +1,31 @@
|
||||
package com.vexsoftware.votifier.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.NuVotifierBukkit;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
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 org.json.JSONObject;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class BukkitPluginMessagingForwardingSink implements ForwardingVoteSink, PluginMessageListener {
|
||||
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.listener = listener;
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
private final Plugin p;
|
||||
private final String channel;
|
||||
private final ForwardedVoteListener listener;
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
@@ -37,10 +35,7 @@ public class BukkitPluginMessagingForwardingSink implements ForwardingVoteSink,
|
||||
@Override
|
||||
public void onPluginMessageReceived(String s, Player player, byte[] bytes) {
|
||||
try {
|
||||
String message = new String(bytes, StandardCharsets.UTF_8);
|
||||
JSONObject jsonObject = new JSONObject(message);
|
||||
Vote v = new Vote(jsonObject);
|
||||
listener.onForward(v);
|
||||
this.handlePluginMessage(bytes);
|
||||
} catch (Exception e) {
|
||||
NuVotifierBukkit.getInstance().getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.vexsoftware.votifier.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);
|
||||
|
||||
}
|
||||
@@ -9,10 +9,6 @@ port: 8192
|
||||
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
|
||||
debug: false
|
||||
|
||||
# Setting this value to false will turn off the votifier port listening for external votes. This is beneficial if the server
|
||||
# is only listening for votes coming from NuVotifier running on your BungeeCord.
|
||||
enableExternal: 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.
|
||||
@@ -30,4 +26,4 @@ forwarding:
|
||||
# - pluginMessaging - Sets up plugin messaging
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
channel: nuvotifier:votes
|
||||
@@ -1,5 +1,13 @@
|
||||
name: Votifier
|
||||
main: com.vexsoftware.votifier.NuVotifierBukkit
|
||||
version: maven-version-number
|
||||
version: @app.version@
|
||||
description: A plugin that gets notified when votes are made for the server on toplists.
|
||||
authors: [blakeman8192, Kramer, tuxed]
|
||||
authors: [ParallelBlock, blakeman8192, Kramer, tuxed]
|
||||
api-version: 1.13
|
||||
|
||||
commands:
|
||||
nvreload:
|
||||
description: Reloads the NuVotifier configuration
|
||||
permission: nuvotifier.console
|
||||
permission-message: "You do not have permission to run this command."
|
||||
usage: "/nvreload"
|
||||
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
compileOnly group: "net.md-5", name: "bungeecord-api", version: "1.8-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.4</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-bungeecord</artifactId>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>maven-replacer-plugin</artifactId>
|
||||
<version>1.3.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>replace</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<file>target/classes/bungee.yml</file>
|
||||
<replacements>
|
||||
<replacement>
|
||||
<token>maven-version-number</token>
|
||||
<value>${project.version}</value>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-common</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
|
||||
public class BungeeBackendServer implements BackendServer {
|
||||
private final ServerInfo info;
|
||||
|
||||
public BungeeBackendServer(ServerInfo info) {
|
||||
this.info = Preconditions.checkNotNull(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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,33 +4,32 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
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.bungee.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.OnlineForwardPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.PluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.MemoryVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
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.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
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.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
@@ -40,31 +39,23 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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, VotifierPlugin {
|
||||
public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlugin {
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
@@ -86,15 +77,19 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
*/
|
||||
private ForwardingVoteSource forwardingMethod;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
private VotifierScheduler scheduler;
|
||||
private LoggingAdapter pluginLogger;
|
||||
|
||||
private void loadAndBind() {
|
||||
scheduler = new BungeeScheduler(this);
|
||||
pluginLogger = new JavaUtilLogger(getLogger());
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
// Handle configuration.
|
||||
File config = new File(getDataFolder() + "/config.yml");
|
||||
File rsaDirectory = new File(getDataFolder() + "/rsa");
|
||||
File config = new File(getDataFolder() , "config.yml");
|
||||
File rsaDirectory = new File(getDataFolder() , "rsa");
|
||||
Configuration configuration;
|
||||
|
||||
if (!config.exists()) {
|
||||
@@ -108,7 +103,7 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token);
|
||||
Files.write(cfgStr, config, StandardCharsets.UTF_8);
|
||||
Files.asCharSink(config, StandardCharsets.UTF_8).write(cfgStr);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
@@ -197,41 +192,9 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
|
||||
// Must set up server asynchronously due to BungeeCord goofiness.
|
||||
FutureTask<?> initTask = new FutureTask<>(Executors.callable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
serverGroup = new NioEventLoopGroup(2);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(NuVotifier.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(NuVotifier.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
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 {
|
||||
@@ -251,22 +214,31 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
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(ProxyServer.getInstance().getServers().size());
|
||||
voteCache = new MemoryVoteCache(
|
||||
ProxyServer.getInstance().getServers().size(), 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(ProxyServer.getInstance().getServers().size(), this, new File(getDataFolder(),
|
||||
fwdCfg.getString("pluginMessaging.file.name")));
|
||||
voteCache = new FileVoteCache(
|
||||
ProxyServer.getInstance().getServers().size(), 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")) {
|
||||
|
||||
List<String> ignoredServers = fwdCfg.getStringList("pluginMessaging.excludedServers");
|
||||
|
||||
try {
|
||||
forwardingMethod = new PluginMessagingForwardingSource(channel, ignoredServers, this, voteCache);
|
||||
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);
|
||||
@@ -275,7 +247,7 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
try {
|
||||
String fallbackServer = fwdCfg.getString("pluginMessaging.joinedServerFallback", null);
|
||||
if (fallbackServer != null && fallbackServer.isEmpty()) fallbackServer = null;
|
||||
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, voteCache, fallbackServer);
|
||||
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, filter, voteCache, fallbackServer, dumpRate);
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
@@ -292,36 +264,88 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
getLogger().info("Address " + section.getString("address") + " couldn't be looked up. Ignoring!");
|
||||
continue;
|
||||
}
|
||||
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
|
||||
new InetSocketAddress(address, section.getShort("port")),
|
||||
KeyCreator.createKeyFrom(section.getString("token",section.getString("key"))));
|
||||
serverList.add(server);
|
||||
|
||||
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 = new ProxyForwardingVoteSource(this, serverGroup, serverList, null);
|
||||
forwardingMethod = new ProxyForwardingVoteSource(this, bootstrap::client, 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 onDisable() {
|
||||
public void onEnable() {
|
||||
loadAndBind();
|
||||
|
||||
getProxy().getPluginManager().registerCommand(this, new Command("nvreload") {
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (sender instanceof ProxiedPlayer)
|
||||
sender.sendMessage(ChatColor.RED + "For security and stability, only console may run this command!");
|
||||
else
|
||||
reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void halt() {
|
||||
// Shut down the network handlers.
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
forwardingMethod = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void 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.");
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
halt();
|
||||
getLogger().info("Votifier disabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
@@ -330,28 +354,23 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getProxy().getPluginManager().callEvent(new VotifierEvent(vote));
|
||||
}
|
||||
});
|
||||
getProxy().getScheduler().runAsync(this, () -> getProxy().getPluginManager().callEvent(new VotifierEvent(vote)));
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
forwardingMethod.forward(vote);
|
||||
}
|
||||
});
|
||||
getProxy().getScheduler().runAsync(this, () -> forwardingMethod.forward(vote));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, Throwable throwable) {
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
if (debug) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
if (alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
|
||||
}
|
||||
}
|
||||
@@ -367,11 +386,29 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return getDescription().getVersion();
|
||||
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);
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
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)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
+9
-10
@@ -1,24 +1,23 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
|
||||
|
||||
public PluginMessagingForwardingSource(String channel, List<String> ignoredServers, NuVotifier nuVotifier, VoteCache cache) {
|
||||
super(channel, ignoredServers, nuVotifier, cache);
|
||||
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) {
|
||||
super(channel, nuVotifier, voteCache);
|
||||
protected PluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache, int dumpRate) {
|
||||
super(channel, nuVotifier, voteCache, dumpRate);
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
@@ -29,6 +28,6 @@ public class PluginMessagingForwardingSource extends AbstractPluginMessagingForw
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
handleServerConnected(e);
|
||||
onServerConnect(new BungeeBackendServer(e.getServer().getInfo()));
|
||||
}
|
||||
}
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSource implements ForwardingVoteSource {
|
||||
|
||||
public AbstractPluginMessagingForwardingSource(String channel, List<String> ignoredServers, NuVotifier nuVotifier, VoteCache cache) {
|
||||
ProxyServer.getInstance().registerChannel(channel);
|
||||
this.channel = channel;
|
||||
this.nuVotifier = nuVotifier;
|
||||
this.cache = cache;
|
||||
this.ignoredServers = ignoredServers;
|
||||
}
|
||||
|
||||
protected AbstractPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache) {
|
||||
this(channel, null, nuVotifier, voteCache);
|
||||
}
|
||||
|
||||
protected final NuVotifier nuVotifier;
|
||||
protected final String channel;
|
||||
protected final VoteCache cache;
|
||||
protected final List<String> ignoredServers;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
for (ServerInfo s : ProxyServer.getInstance().getServers().values()) {
|
||||
if (ignoredServers != null && ignoredServers.contains(s.getName())) continue;
|
||||
if (!forwardSpecific(s, rawData)) attemptToAddToCache(v, s.getName());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean forwardSpecific(ServerInfo connection, Vote vote) {
|
||||
byte[] rawData = vote.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
return connection.sendData(channel, rawData, false);
|
||||
}
|
||||
|
||||
private boolean forwardSpecific(ServerInfo connection, byte[] data) {
|
||||
return connection.sendData(channel, data, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
ProxyServer.getInstance().unregisterChannel(channel);
|
||||
|
||||
if (cache != null && cache instanceof FileVoteCache) {
|
||||
try {
|
||||
((FileVoteCache) cache).halt();
|
||||
} catch (IOException e) {
|
||||
nuVotifier.getLogger().log(Level.SEVERE, "Unable to save cached votes, votes will be lost.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleServerConnected(final ServerConnectedEvent e) {
|
||||
if (cache == null) return;
|
||||
final String serverName = e.getServer().getInfo().getName();
|
||||
final Collection<Vote> cachedVotes = cache.evict(serverName);
|
||||
if (!cachedVotes.isEmpty()) {
|
||||
nuVotifier.getProxy().getScheduler().schedule(nuVotifier, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int evicted = 0;
|
||||
int unsuccessfulEvictions = 0;
|
||||
for (Vote v : cachedVotes) {
|
||||
if (forwardSpecific(e.getServer().getInfo(), v)) {
|
||||
evicted++;
|
||||
} else {
|
||||
// Re-add to cache to send later.
|
||||
cache.addToCache(v, serverName);
|
||||
unsuccessfulEvictions++;
|
||||
}
|
||||
}
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Successfully evicted " + evicted + " votes to server '" + serverName + "'.");
|
||||
if (unsuccessfulEvictions > 0) {
|
||||
nuVotifier.getLogger().info("Held " + unsuccessfulEvictions + " votes for server '" + serverName + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
protected void attemptToAddToCache(Vote v, String server) {
|
||||
if (cache != null) {
|
||||
cache.addToCache(v, server);
|
||||
if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().info("Added to forwarding cache: " + v + " -> " + server);
|
||||
} else if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().severe("Could not immediately send vote to backend, vote lost! " + v + " -> " + server);
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
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, VoteCache cache, String fallbackServer) {
|
||||
super(channel, nuVotifier, cache);
|
||||
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 || !forwardSpecific(p.getServer().getInfo(), v)) {
|
||||
ServerInfo serverInfo = ProxyServer.getInstance().getServers().get(fallbackServer);
|
||||
if (serverInfo != null && !forwardSpecific(serverInfo, v)) {
|
||||
attemptToAddToCache(v, fallbackServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The below is to allow us to share code since the event handlers from the parent class don't get
|
||||
// picked up.
|
||||
@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.
|
||||
handleServerConnected(e);
|
||||
}
|
||||
}
|
||||
Vendored
-87
@@ -1,87 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONTokener;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class FileVoteCache extends MemoryVoteCache {
|
||||
|
||||
private final File cacheFile;
|
||||
private final ScheduledTask saveTask;
|
||||
|
||||
public FileVoteCache(int initialMemorySize, final Plugin plugin, File cacheFile) throws IOException {
|
||||
super(initialMemorySize);
|
||||
this.cacheFile = cacheFile;
|
||||
load();
|
||||
|
||||
saveTask = ProxyServer.getInstance().getScheduler().schedule(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
save();
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Unable to save cached votes, votes will be lost if you restart.", e);
|
||||
}
|
||||
}
|
||||
}, 3, 3, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void load() throws IOException {
|
||||
// Load the cache from disk
|
||||
JSONObject object;
|
||||
try (BufferedReader reader = Files.newReader(cacheFile, StandardCharsets.UTF_8)) {
|
||||
object = new JSONObject(new JSONTokener(reader));
|
||||
} catch (FileNotFoundException e) {
|
||||
object = new JSONObject();
|
||||
}
|
||||
|
||||
// Deserialize all votes contained
|
||||
for (Object server : object.keySet()) {
|
||||
JSONArray voteArray = object.getJSONArray(((String) server));
|
||||
List<Vote> votes = new ArrayList<>(voteArray.length());
|
||||
for (int i = 0; i < voteArray.length(); i++) {
|
||||
JSONObject voteObject = voteArray.getJSONObject(i);
|
||||
votes.add(new Vote(voteObject));
|
||||
}
|
||||
voteCache.put(((String) server), votes);
|
||||
}
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
cacheLock.lock();
|
||||
JSONObject votesObject = new JSONObject();
|
||||
try {
|
||||
// Create a copy of the votes.
|
||||
for (Map.Entry<String, Collection<Vote>> entry : voteCache.entrySet()) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (Vote vote : entry.getValue()) {
|
||||
array.put(vote.serialize());
|
||||
}
|
||||
votesObject.put(entry.getKey(), array);
|
||||
}
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) {
|
||||
votesObject.write(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public void halt() throws IOException {
|
||||
saveTask.cancel();
|
||||
save();
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
-60
@@ -1,60 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class MemoryVoteCache implements VoteCache {
|
||||
|
||||
public MemoryVoteCache(int initialSize) {
|
||||
voteCache = new HashMap<>(initialSize);
|
||||
}
|
||||
|
||||
protected final Map<String, Collection<Vote>> voteCache;
|
||||
|
||||
protected final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
@Override
|
||||
public Collection<String> getCachedServers() {
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return ImmutableList.copyOf(voteCache.keySet());
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCache(Vote v, String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
Collection<Vote> voteCollection = voteCache.get(server);
|
||||
if (voteCollection == null) {
|
||||
voteCollection = new ArrayList<>();
|
||||
voteCache.put(server, voteCollection);
|
||||
}
|
||||
voteCollection.add(v);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evict(String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
Collection<Vote> fromCollection = voteCache.remove(server);
|
||||
return fromCollection != null ? fromCollection : ImmutableList.<Vote>of();
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: NuVotifier
|
||||
main: com.vexsoftware.votifier.bungee.NuVotifier
|
||||
version: maven-version-number
|
||||
author: blakeman8192, Kramer, tuxed
|
||||
version: @app.version@
|
||||
author: ParallelBlock, blakeman8192, Kramer, tuxed
|
||||
@@ -26,21 +26,22 @@ forwarding:
|
||||
# - proxy - Proxies votes to other NuVotifier servers from this server.
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
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, this option is ignored.
|
||||
|
||||
# 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.
|
||||
# This method is NOT recommended for production systems!
|
||||
# - 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
|
||||
@@ -50,9 +51,17 @@ forwarding:
|
||||
#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:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies {
|
||||
compile group: "io.netty", name: "netty-handler", version: "4.1.16.Final"
|
||||
compile 'com.google.code.gson:gson:2.8.0'
|
||||
compile 'com.google.guava:guava:24.1-jre'
|
||||
testCompile group: "junit", name: "junit", version: "4.12"
|
||||
testCompile group: "org.json", name: "json", version: "20180130" // retain this for testing reasons
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.4</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-common</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Netty Dependency -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-handler</artifactId>
|
||||
<version>4.0.31.Final</version>
|
||||
<type>jar</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- JSON Dependency -->
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20140107</version>
|
||||
<type>jar</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- JUnit, for tests -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -7,5 +7,14 @@ import io.netty.channel.Channel;
|
||||
public interface VoteHandler {
|
||||
void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception;
|
||||
|
||||
void onError(Channel channel, Throwable throwable);
|
||||
// these default members are kept for compatibility sake - there probably isn't anyone out there implementing these
|
||||
// on their own, but we still deprecate them before removing them because we are nice!
|
||||
default void onError(Channel channel, boolean voteAlreadyCompleted, Throwable throwable) {
|
||||
onError(channel, throwable);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
default void onError(Channel channel, Throwable throwable) {
|
||||
throw new RuntimeException("Unimplemented onError handler");
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VotifierPlugin {
|
||||
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
|
||||
|
||||
Map<String, Key> getTokens();
|
||||
|
||||
KeyPair getProtocolV1Key();
|
||||
|
||||
String getVersion();
|
||||
}
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
package com.vexsoftware.votifier.model;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* A model for a vote.
|
||||
@@ -47,33 +48,50 @@ public class Vote {
|
||||
*/
|
||||
private String timeStamp;
|
||||
|
||||
/**
|
||||
* Timestamp (unix-millis) normalized and taken from a known source
|
||||
*/
|
||||
private final long localTimestamp;
|
||||
|
||||
@Deprecated
|
||||
public Vote() {
|
||||
localTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp) {
|
||||
this(serviceName, username, address, timeStamp, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp, long localTimestamp) {
|
||||
this.serviceName = serviceName;
|
||||
this.username = username;
|
||||
this.address = address;
|
||||
this.timeStamp = timeStamp;
|
||||
this.localTimestamp = localTimestamp;
|
||||
}
|
||||
|
||||
private static String getTimestamp(JSONObject object) {
|
||||
private static String getTimestamp(JsonElement object) {
|
||||
try {
|
||||
return Long.toString(object.getLong("timestamp"));
|
||||
return Long.toString(object.getAsLong());
|
||||
} catch (Exception e) {
|
||||
return object.getString("timestamp");
|
||||
return object.getAsString();
|
||||
}
|
||||
}
|
||||
|
||||
public Vote(JSONObject jsonObject) {
|
||||
this(jsonObject.getString("serviceName"), jsonObject.getString("username"), jsonObject.getString("address"), getTimestamp(jsonObject));
|
||||
public Vote(JsonObject jsonObject) {
|
||||
this(jsonObject.get("serviceName").getAsString(),
|
||||
jsonObject.get("username").getAsString(),
|
||||
jsonObject.get("address").getAsString(),
|
||||
getTimestamp(jsonObject.get("timestamp")),
|
||||
// maintained for backwards compatibility with <2.3.6 peers
|
||||
(jsonObject.has("localTimestamp") ? jsonObject.get("localTimestamp").getAsLong() : System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vote (from:" + serviceName + " username:" + username
|
||||
+ " address:" + address + " timeStamp:" + timeStamp + ")";
|
||||
+ " address:" + address + " timeStamp:" + timeStamp
|
||||
+ " localTimestamp:" + localTimestamp + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,12 +170,22 @@ public class Vote {
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
public JSONObject serialize() {
|
||||
JSONObject ret = new JSONObject();
|
||||
ret.put("serviceName", serviceName);
|
||||
ret.put("username", username);
|
||||
ret.put("address", address);
|
||||
ret.put("timestamp", timeStamp);
|
||||
/**
|
||||
* Gets the local timestamp, in unix-millis. Calculated locally by a NuVotifier instance
|
||||
*
|
||||
* @return The local timestamp
|
||||
*/
|
||||
public long getLocalTimestamp() {
|
||||
return localTimestamp;
|
||||
}
|
||||
|
||||
public JsonObject serialize() {
|
||||
JsonObject ret = new JsonObject();
|
||||
ret.addProperty("serviceName", serviceName);
|
||||
ret.addProperty("username", username);
|
||||
ret.addProperty("address", address);
|
||||
ret.addProperty("timestamp", timeStamp);
|
||||
ret.addProperty("localTimestamp", localTimestamp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -172,7 +200,6 @@ public class Vote {
|
||||
if (!username.equals(vote.username)) return false;
|
||||
if (!address.equals(vote.address)) return false;
|
||||
return timeStamp.equals(vote.timeStamp);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.vexsoftware.votifier.net;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
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 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.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class VotifierServerBootstrap {
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final NioEventLoopGroup bossLoopGroup;
|
||||
private final NioEventLoopGroup 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;
|
||||
this.bossLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO boss").build());
|
||||
this.eventLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO worker").build());
|
||||
}
|
||||
|
||||
public void start(Consumer<Throwable> error) {
|
||||
Objects.requireNonNull(error, "error");
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(bossLoopGroup, eventLoopGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(plugin);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !v1Disable));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(plugin));
|
||||
}
|
||||
})
|
||||
.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, future.cause());
|
||||
error.accept(future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Bootstrap client() {
|
||||
return new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(eventLoopGroup);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ public class VotifierSession {
|
||||
public static final AttributeKey<VotifierSession> KEY = AttributeKey.valueOf("votifier_session");
|
||||
private ProtocolVersion version = ProtocolVersion.UNKNOWN;
|
||||
private final String challenge;
|
||||
private boolean hasCompletedVote = false;
|
||||
|
||||
public VotifierSession() {
|
||||
challenge = TokenUtil.newToken();
|
||||
@@ -27,6 +28,17 @@ public class VotifierSession {
|
||||
return challenge;
|
||||
}
|
||||
|
||||
public void completeVote() {
|
||||
if (hasCompletedVote)
|
||||
throw new IllegalStateException("Protocol completed vote twice!");
|
||||
|
||||
hasCompletedVote = true;
|
||||
}
|
||||
|
||||
public boolean hasCompletedVote() {
|
||||
return hasCompletedVote;
|
||||
}
|
||||
|
||||
public enum ProtocolVersion {
|
||||
UNKNOWN,
|
||||
ONE,
|
||||
|
||||
+14
-11
@@ -1,12 +1,13 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
private final VoteHandler handler;
|
||||
@@ -20,28 +21,30 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
handler.onVoteReceived(ctx.channel(), vote, session.getVersion());
|
||||
session.completeVote();
|
||||
|
||||
if (session.getVersion() == VotifierSession.ProtocolVersion.ONE) {
|
||||
ctx.close();
|
||||
} else {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("status", "ok");
|
||||
ctx.writeAndFlush(object.toString() + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("status", "ok");
|
||||
ctx.writeAndFlush(GsonInst.gson.toJson(object) + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
handler.onError(ctx.channel(), cause);
|
||||
handler.onError(ctx.channel(), session.hasCompletedVote(), cause);
|
||||
|
||||
if (session.getVersion() == VotifierSession.ProtocolVersion.TWO) {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("status", "error");
|
||||
object.put("cause", cause.getClass().getSimpleName());
|
||||
object.put("error", cause.getMessage());
|
||||
ctx.writeAndFlush(object.toString() + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("status", "error");
|
||||
object.addProperty("cause", cause.getClass().getSimpleName());
|
||||
object.addProperty("error", cause.getMessage());
|
||||
ctx.writeAndFlush(GsonInst.gson.toJson(object) + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
+2
-5
@@ -1,6 +1,5 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@@ -14,11 +13,9 @@ import java.nio.charset.StandardCharsets;
|
||||
*/
|
||||
public class VotifierGreetingHandler extends ChannelInboundHandlerAdapter {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
|
||||
String version = "VOTIFIER " + plugin.getVersion() + " " + session.getChallenge() + "\n";
|
||||
String version = "VOTIFIER 2 " + session.getChallenge() + "\n";
|
||||
ByteBuf versionBuf = Unpooled.copiedBuffer(version, StandardCharsets.UTF_8);
|
||||
ctx.writeAndFlush(versionBuf);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
*/
|
||||
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
if (buf.readableBytes() < 256) {
|
||||
return;
|
||||
}
|
||||
|
||||
+15
-14
@@ -1,19 +1,19 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
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 io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -25,42 +25,43 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, String s, List<Object> list) throws Exception {
|
||||
JSONObject voteMessage = new JSONObject(s);
|
||||
JsonObject voteMessage = GsonInst.gson.fromJson(s, JsonObject.class);
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
// Deserialize the payload.
|
||||
JSONObject votePayload = new JSONObject(voteMessage.getString("payload"));
|
||||
String payload = voteMessage.get("payload").getAsString();
|
||||
JsonObject votePayload = GsonInst.gson.fromJson(payload, JsonObject.class);
|
||||
|
||||
// Verify challenge.
|
||||
if (!votePayload.getString("challenge").equals(session.getChallenge())) {
|
||||
if (!votePayload.get("challenge").getAsString().equals(session.getChallenge())) {
|
||||
throw new CorruptedFrameException("Challenge is not valid");
|
||||
}
|
||||
|
||||
// Verify that we have keys available.
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
Key key = plugin.getTokens().get(votePayload.getString("serviceName"));
|
||||
Key key = plugin.getTokens().get(votePayload.get("serviceName").getAsString());
|
||||
|
||||
if (key == null) {
|
||||
key = plugin.getTokens().get("default");
|
||||
if (key == null) {
|
||||
throw new RuntimeException("Unknown service '" + votePayload.getString("serviceName") + "'");
|
||||
throw new RuntimeException("Unknown service '" + votePayload.get("serviceName").getAsString() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// Verify signature.
|
||||
String sigHash = voteMessage.getString("signature");
|
||||
byte[] sigBytes = DatatypeConverter.parseBase64Binary(sigHash);
|
||||
String sigHash = voteMessage.get("signature").getAsString();
|
||||
byte[] sigBytes = Base64.getDecoder().decode(sigHash);
|
||||
|
||||
if (!hmacEqual(sigBytes, voteMessage.getString("payload").getBytes(StandardCharsets.UTF_8), key)) {
|
||||
if (!hmacEqual(sigBytes, payload.getBytes(StandardCharsets.UTF_8), key)) {
|
||||
throw new CorruptedFrameException("Signature is not valid (invalid token?)");
|
||||
}
|
||||
|
||||
// Stopgap: verify the "uuid" field is valid, if provided.
|
||||
if (votePayload.has("uuid")) {
|
||||
UUID.fromString(votePayload.getString("uuid"));
|
||||
UUID.fromString(votePayload.get("uuid").getAsString());
|
||||
}
|
||||
|
||||
if (votePayload.getString("username").length() > 16) {
|
||||
if (votePayload.get("username").getAsString().length() > 16) {
|
||||
throw new CorruptedFrameException("Username too long");
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
// Determine the number of bytes that are available.
|
||||
int readable = buf.readableBytes();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
package com.vexsoftware.votifier.net.protocol.v1crypto;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.util.Base64;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -53,10 +53,9 @@ public class RSAIO {
|
||||
privateKey.getEncoded());
|
||||
try (FileOutputStream publicOut = new FileOutputStream(directory + "/public.key");
|
||||
FileOutputStream privateOut = new FileOutputStream(directory + "/private.key")) {
|
||||
publicOut.write(DatatypeConverter.printBase64Binary(publicSpec.getEncoded())
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
privateOut.write(DatatypeConverter.printBase64Binary(privateSpec.getEncoded())
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
publicOut.write(Base64.getEncoder().encode(publicSpec.getEncoded()));
|
||||
privateOut.write(Base64.getEncoder().encode(privateSpec.getEncoded()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,16 +69,14 @@ public class RSAIO {
|
||||
*/
|
||||
public static KeyPair load(File directory) throws Exception {
|
||||
// Read the public key file.
|
||||
File publicKeyFile = new File(directory + "/public.key");
|
||||
File publicKeyFile = new File(directory, "public.key");
|
||||
byte[] encodedPublicKey = Files.readAllBytes(publicKeyFile.toPath());
|
||||
encodedPublicKey = DatatypeConverter.parseBase64Binary(new String(
|
||||
encodedPublicKey, StandardCharsets.UTF_8));
|
||||
encodedPublicKey = Base64.getDecoder().decode(new String(encodedPublicKey, StandardCharsets.UTF_8));
|
||||
|
||||
// Read the private key file.
|
||||
File privateKeyFile = new File(directory + "/private.key");
|
||||
File privateKeyFile = new File(directory, "private.key");
|
||||
byte[] encodedPrivateKey = Files.readAllBytes(privateKeyFile.toPath());
|
||||
encodedPrivateKey = DatatypeConverter.parseBase64Binary(new String(
|
||||
encodedPrivateKey, StandardCharsets.UTF_8));
|
||||
encodedPrivateKey = Base64.getDecoder().decode(new String(encodedPrivateKey, StandardCharsets.UTF_8));
|
||||
|
||||
// Instantiate and return the key pair.
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
public interface BackendServer {
|
||||
String getName();
|
||||
|
||||
boolean sendPluginMessage(String channel, byte[] data);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class JavaUtilLogger implements LoggingAdapter {
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
public JavaUtilLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s) {
|
||||
logger.severe(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s, Object... o) {
|
||||
logger.log(Level.SEVERE, s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s) {
|
||||
logger.warning(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s, Object... o) {
|
||||
logger.log(Level.WARNING, s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s) {
|
||||
logger.info(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s, Object... o) {
|
||||
logger.log(Level.INFO, s, o);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
public interface LoggingAdapter {
|
||||
void error(String s);
|
||||
void error(String s, Object... o);
|
||||
|
||||
void warn(String s);
|
||||
void warn(String s, Object... o);
|
||||
|
||||
void info(String s);
|
||||
void info(String s, Object... o);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ProxyVotifierPlugin extends VotifierPlugin {
|
||||
Collection<BackendServer> getAllBackendServers();
|
||||
|
||||
Optional<BackendServer> getServer(String name);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ScheduledExecutorServiceVotifierScheduler implements VotifierScheduler {
|
||||
private final ScheduledExecutorService service;
|
||||
|
||||
public ScheduledExecutorServiceVotifierScheduler(ScheduledExecutorService service) {
|
||||
this.service = Preconditions.checkNotNull(service, "service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return new ScheduledVotifierTaskWrapper(service.submit(runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new ScheduledVotifierTaskWrapper(service.submit(runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.schedule(runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.schedule(runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.scheduleAtFixedRate(runnable, delay, repeat, unit));
|
||||
}
|
||||
|
||||
private static class ScheduledVotifierTaskWrapper implements ScheduledVotifierTask {
|
||||
private final Future<?> future;
|
||||
|
||||
private ScheduledVotifierTaskWrapper(Future<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
future.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
public interface ScheduledVotifierTask {
|
||||
void cancel();
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface VotifierScheduler {
|
||||
ScheduledVotifierTask sync(Runnable runnable);
|
||||
|
||||
ScheduledVotifierTask onPool(Runnable runnable);
|
||||
|
||||
ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit);
|
||||
|
||||
ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit);
|
||||
|
||||
ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}, 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
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.sponge.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
/**
|
||||
* Represents a method at which to receive forwarded votes.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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.EMPTY_SET, true);
|
||||
}
|
||||
|
||||
private final Collection<String> names;
|
||||
private final boolean whitelist;
|
||||
|
||||
public boolean isAllowed(String name) {
|
||||
return names.contains(name) == whitelist;
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
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 com.vexsoftware.votifier.util.GsonInst;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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(int initialMemorySize, final VotifierPlugin plugin, File cacheFile, long voteTTL) throws IOException {
|
||||
super(initialMemorySize, 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.newReader(cacheFile, StandardCharsets.UTF_8)) {
|
||||
object = GsonInst.gson.fromJson(reader, JsonObject.class);
|
||||
} catch (FileNotFoundException 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.putAll(player, readVotes(players.getAsJsonArray(player)));
|
||||
}
|
||||
|
||||
for (String server : keySet(servers)) {
|
||||
voteCache.putAll(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<Vote> readVotes(JsonArray voteArray) {
|
||||
List<Vote> votes = new ArrayList<>(voteArray.size());
|
||||
for (int i = 0; i < voteArray.size(); i++) {
|
||||
JsonObject voteObject = voteArray.get(i).getAsJsonObject();
|
||||
Vote v = new Vote(voteObject);
|
||||
if (hasTimedOut(v))
|
||||
l.warn("Purging out of date vote.", v);
|
||||
else
|
||||
votes.add(v);
|
||||
}
|
||||
return votes;
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
cacheLock.lock();
|
||||
JsonObject votesObject = new JsonObject();
|
||||
votesObject.addProperty("version", 2);
|
||||
try {
|
||||
votesObject.add("players", serializeMap(playerVoteCache.asMap()));
|
||||
votesObject.add("servers", serializeMap(voteCache.asMap()));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) {
|
||||
GsonInst.gson.toJson(votesObject, writer);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonObject serializeMap(Map<String, Collection<Vote>> map) {
|
||||
JsonObject o = new JsonObject();
|
||||
|
||||
Iterator<Map.Entry<String, Collection<Vote>>> entryItr = map.entrySet().iterator();
|
||||
while (entryItr.hasNext()) {
|
||||
Map.Entry<String, Collection<Vote>> entry = entryItr.next();
|
||||
JsonArray array = new JsonArray();
|
||||
Iterator<Vote> voteItr = entry.getValue().iterator();
|
||||
while (voteItr.hasNext()) {
|
||||
Vote 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();
|
||||
}
|
||||
}
|
||||
Vendored
+115
@@ -0,0 +1,115 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Multimap;
|
||||
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 Multimap<String, Vote> voteCache;
|
||||
protected final Multimap<String, Vote> playerVoteCache;
|
||||
|
||||
protected final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
private final ScheduledVotifierTask sweepTask;
|
||||
|
||||
public MemoryVoteCache(int initialSize, VotifierPlugin p, long voteTTL) {
|
||||
voteCache = HashMultimap.create(initialSize, 10); // 10 is a complete guess. I have no idea .-.
|
||||
playerVoteCache = HashMultimap.create();
|
||||
|
||||
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 ImmutableList.copyOf(voteCache.keySet());
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCache(Vote v, String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
voteCache.put(server, v);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCachePlayer(Vote v, String player) {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
playerVoteCache.put(player, v);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evictPlayer(String player) {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return new HashSet<>(playerVoteCache.removeAll(player));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evict(String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return new HashSet<>(voteCache.removeAll(server));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void sweep() {
|
||||
sweep(voteCache);
|
||||
sweep(playerVoteCache);
|
||||
}
|
||||
|
||||
private void sweep(Multimap<?, Vote> m) {
|
||||
Iterator<Vote> vi = m.values().iterator();
|
||||
while (vi.hasNext()) {
|
||||
Vote v = vi.next();
|
||||
if (hasTimedOut(v)) {
|
||||
l.warn("Purging out of date vote.", v);
|
||||
vi.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasTimedOut(Vote v) {
|
||||
if (voteTTL == -1) return false;
|
||||
// scale voteTTL to milliseconds
|
||||
return v.getLocalTimestamp() + voteTTL * 24 * 60 * 60 * 1000 < System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
+23
-1
@@ -1,8 +1,9 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
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.
|
||||
@@ -24,6 +25,16 @@ public interface VoteCache {
|
||||
*/
|
||||
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
|
||||
*
|
||||
@@ -31,4 +42,15 @@ public interface VoteCache {
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
+22
-34
@@ -1,17 +1,15 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierProtocol2Encoder;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierResponseHandler;
|
||||
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.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.Delimiters;
|
||||
@@ -23,20 +21,20 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
private static final int MAX_RETRIES = 5;
|
||||
private final NuVotifier plugin;
|
||||
private final NioEventLoopGroup eventLoopGroup;
|
||||
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(NuVotifier plugin, NioEventLoopGroup eventLoopGroup, List<BackendServer> backendServers, VoteCache voteCache) {
|
||||
public ProxyForwardingVoteSource(VotifierPlugin plugin, Supplier<Bootstrap> nettyBootstrap, List<BackendServer> backendServers, VoteCache voteCache) {
|
||||
this.plugin = plugin;
|
||||
this.eventLoopGroup = eventLoopGroup;
|
||||
this.nettyBootstrap = nettyBootstrap;
|
||||
this.backendServers = backendServers;
|
||||
this.voteCache = voteCache;
|
||||
}
|
||||
@@ -54,21 +52,19 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
|
||||
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
|
||||
new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(eventLoopGroup)
|
||||
nettyBootstrap.get()
|
||||
.handler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(8, TimeUnit.SECONDS));
|
||||
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.getLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
|
||||
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,12 +76,9 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
})
|
||||
.connect(server.address)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -106,18 +99,13 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getLogger().log(Level.SEVERE, msg, cause);
|
||||
plugin.getPluginLogger().error(msg, cause);
|
||||
} else {
|
||||
plugin.getLogger().log(Level.SEVERE, msg);
|
||||
plugin.getPluginLogger().error(msg);
|
||||
}
|
||||
|
||||
if (willRetry) {
|
||||
plugin.getProxy().getScheduler().schedule(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
forwardVote(server, v, tries + 1);
|
||||
}
|
||||
}, nextDelay, TimeUnit.SECONDS);
|
||||
plugin.getScheduler().delayedOnPool(() -> forwardVote(server, v, tries + 1), nextDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
+9
-9
@@ -1,15 +1,15 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
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 org.json.JSONObject;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
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;
|
||||
@@ -22,18 +22,18 @@ public class VotifierProtocol2Encoder extends MessageToByteEncoder<VoteRequest>
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, VoteRequest req, ByteBuf buf) throws Exception {
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payloadObject = req.getVote().serialize();
|
||||
payloadObject.put("challenge", req.getChallenge());
|
||||
JsonObject object = new JsonObject();
|
||||
JsonObject payloadObject = req.getVote().serialize();
|
||||
payloadObject.addProperty("challenge", req.getChallenge());
|
||||
String payload = payloadObject.toString();
|
||||
object.put("payload", payload);
|
||||
object.addProperty("payload", payload);
|
||||
|
||||
// Generate the MAC
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
mac.update(payload.getBytes(StandardCharsets.UTF_8));
|
||||
String computed = DatatypeConverter.printBase64Binary(mac.doFinal());
|
||||
object.put("signature", computed);
|
||||
String computed = Base64.getEncoder().encodeToString(mac.doFinal());
|
||||
object.addProperty("signature", computed);
|
||||
|
||||
// JSON message is ready for encoding.
|
||||
String finalMessage = object.toString();
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
@@ -9,16 +9,16 @@ import io.netty.handler.codec.CorruptedFrameException;
|
||||
public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final Vote toSend;
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
private final NuVotifier nuVotifier;
|
||||
private final VotifierPlugin nuVotifier;
|
||||
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, NuVotifier 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) throws Exception {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String s) {
|
||||
String[] handshakeContents = s.split(" ");
|
||||
if (handshakeContents.length != 3) {
|
||||
throw new CorruptedFrameException("Handshake is not valid.");
|
||||
@@ -26,7 +26,7 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
|
||||
|
||||
VoteRequest request = new VoteRequest(handshakeContents[2], toSend);
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Sent request: " + request.toString());
|
||||
nuVotifier.getPluginLogger().info("Sent request: " + request.toString());
|
||||
}
|
||||
ctx.writeAndFlush(request);
|
||||
ctx.pipeline().addLast(new VotifierProtocol2ResponseHandler(responseHandler));
|
||||
@@ -35,7 +35,7 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
|
||||
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
+9
-7
@@ -1,8 +1,9 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
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;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
@@ -12,19 +13,20 @@ public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandle
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
JSONObject object = new JSONObject(msg);
|
||||
String status = object.getString("status");
|
||||
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.getString("cause") + ": " + object.getString("error")));
|
||||
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) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
public interface VotifierResponseHandler {
|
||||
void onSuccess();
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class GsonInst {
|
||||
|
||||
public static final Gson gson = new GsonBuilder().create();
|
||||
|
||||
}
|
||||
+29
-38
@@ -1,19 +1,14 @@
|
||||
package com.vexsoftware.votifier.util.standalone;
|
||||
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledExecutorServiceVotifierScheduler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.Key;
|
||||
@@ -21,45 +16,36 @@ 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, VoteHandler {
|
||||
public class StandaloneVotifierPlugin implements VotifierPlugin {
|
||||
private final Map<String, Key> tokens;
|
||||
private final VoteReceiver receiver;
|
||||
private final KeyPair v1Key;
|
||||
private final InetSocketAddress bind;
|
||||
private NioEventLoopGroup group;
|
||||
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 Throwable start() throws Throwable {
|
||||
group = new NioEventLoopGroup(2);
|
||||
public void start() {
|
||||
start(o -> {});
|
||||
}
|
||||
|
||||
ChannelFuture future = new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(group)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(StandaloneVotifierPlugin.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, v1Key != null));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(StandaloneVotifierPlugin.this));
|
||||
}
|
||||
})
|
||||
.bind(bind)
|
||||
.syncUninterruptibly();
|
||||
|
||||
if (!future.isSuccess()) {
|
||||
return future.cause();
|
||||
} else {
|
||||
return null;
|
||||
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
|
||||
@@ -73,8 +59,13 @@ public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.3.4";
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return new JavaUtilLogger(Logger.getAnonymousLogger());
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,7 +74,7 @@ public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, Throwable throwable) {
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
receiver.onException(throwable);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
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 javax.xml.bind.DatatypeConverter;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
@@ -12,6 +16,7 @@ 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;
|
||||
|
||||
@@ -23,7 +28,7 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
URL resourceUrl = TestVotifierPlugin.class.getResource(u);
|
||||
Path resourcePath = Paths.get(resourceUrl.toURI());
|
||||
|
||||
return DatatypeConverter.parseBase64Binary(new String(Files.readAllBytes(resourcePath), StandardCharsets.UTF_8));
|
||||
return Base64.getDecoder().decode(new String(Files.readAllBytes(resourcePath), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -71,8 +76,13 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.3";
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void specificKeysOnly() {
|
||||
@@ -84,4 +94,9 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
keyMap.clear();
|
||||
keyMap.put("default", KeyCreator.createKeyFrom("test"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
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;
|
||||
|
||||
+21
-16
@@ -1,8 +1,10 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
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;
|
||||
@@ -11,9 +13,9 @@ import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -32,13 +34,14 @@ public class VotifierProtocol2DecoderTest {
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
payload.put("challenge", SESSION.getChallenge());
|
||||
object.put("payload", payload.toString());
|
||||
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",
|
||||
DatatypeConverter.printBase64Binary(mac.doFinal(payload.toString().getBytes(StandardCharsets.UTF_8))));
|
||||
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
if (expectSuccess) {
|
||||
assertTrue(channel.writeInbound(object.toString()));
|
||||
@@ -59,15 +62,15 @@ public class VotifierProtocol2DecoderTest {
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void testFailureDecodeBadPacket() throws Exception {
|
||||
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.put("challenge", SESSION.getChallenge());
|
||||
object.put("payload", payload.toString());
|
||||
JsonObject payload = vote.serialize();
|
||||
payload.addProperty("challenge", SESSION.getChallenge());
|
||||
object.put("payload", GsonInst.gson.toJson(payload));
|
||||
// We "forget" the signature.
|
||||
|
||||
try {
|
||||
@@ -84,13 +87,14 @@ public class VotifierProtocol2DecoderTest {
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
JsonObject payload = vote.serialize();
|
||||
String payloadEncoded = GsonInst.gson.toJson(payload);
|
||||
// We "forget" the challenge.
|
||||
object.put("payload", payload.toString());
|
||||
object.put("payload", payloadEncoded);
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(TestVotifierPlugin.getI().getTokens().get("default"));
|
||||
object.put("signature",
|
||||
DatatypeConverter.printBase64Binary(mac.doFinal(payload.toString().getBytes(StandardCharsets.UTF_8))));
|
||||
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
@@ -106,14 +110,15 @@ public class VotifierProtocol2DecoderTest {
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
JsonObject payload = vote.serialize();
|
||||
// We provide the wrong challenge.
|
||||
payload.put("challenge", "not a challenge for me");
|
||||
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",
|
||||
DatatypeConverter.printBase64Binary(mac.doFinal(payload.toString().getBytes(StandardCharsets.UTF_8))));
|
||||
Base64.getEncoder().encode(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
|
||||
-5
@@ -25,7 +25,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
test.release();
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@@ -41,7 +40,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.TWO, session.getVersion());
|
||||
test.release();
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@@ -59,7 +57,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
test.release();
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@@ -75,7 +72,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
try {
|
||||
channel.writeInbound(buf);
|
||||
} finally {
|
||||
buf.release();
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
@@ -94,7 +90,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
encryptedByteBuf.release();
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PMForwardingSinkTest {
|
||||
|
||||
@Test
|
||||
public void testSuccessfulMultiDecode() throws Exception {
|
||||
List<Vote> receivedVotes = Lists.newArrayList();
|
||||
ForwardedVoteListener vl = receivedVotes::add;
|
||||
|
||||
List<Vote> sentVotes = Lists.newArrayList(
|
||||
new Vote("serviceA", "usernameA", "1.1.1.1", "1546300800"),
|
||||
new Vote("serviceB", "usernameBBBBBBB", "1.2.23.4", "1514764800", System.currentTimeMillis())
|
||||
);
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
|
||||
for (Vote v : sentVotes) {
|
||||
message.append(v.serialize());
|
||||
}
|
||||
|
||||
byte[] messageBytes = message.toString().getBytes();
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
resources/
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
.PHONY: clean, all, run
|
||||
|
||||
all : resources/paper.jar resources/proxy.jar
|
||||
|
||||
resources :
|
||||
mkdir resources
|
||||
|
||||
resources/paper.jar : resources
|
||||
wget -O resources/paper.jar https://papermc.io/ci/job/Paper-1.13/lastSuccessfulBuild/artifact/paperclip.jar
|
||||
|
||||
resources/proxy.jar : resources
|
||||
wget -O resources/proxy.jar https://papermc.io/ci/job/Waterfall/lastSuccessfulBuild/artifact/Waterfall-Proxy/bootstrap/target/Waterfall.jar
|
||||
|
||||
clean :
|
||||
rm -r resources/
|
||||
|
||||
run :
|
||||
tmux new-session './proxy/run.sh' \; \
|
||||
split-window -h './s1/run.sh' \; \
|
||||
split-window -h './s2/run.sh' \; \
|
||||
select-layout even-horizontal
|
||||
@@ -0,0 +1,26 @@
|
||||
## Using this testbed
|
||||
|
||||
This testbed runs one Bungeecord as well as two Paper 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 | MC Port | Votifier Port |
|
||||
| ------ | ------- | ------------- |
|
||||
| proxy | 25577 | 8192 |
|
||||
| s1 | 25565 | 8193 |
|
||||
| s2 | 25566 | 8194 |
|
||||
@@ -0,0 +1,4 @@
|
||||
logs/
|
||||
modules/
|
||||
locations.yml
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
forge_support: true
|
||||
timeout: 30000
|
||||
online_mode: true
|
||||
log_commands: false
|
||||
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
|
||||
connection_throttle: 1
|
||||
groups:
|
||||
md_5:
|
||||
- admin
|
||||
log_pings: true
|
||||
ip_forward: true
|
||||
prevent_proxy_connections: true
|
||||
network_compression_threshold: 256
|
||||
disabled_commands: []
|
||||
stats: bf8a54ea-2917-4b9e-b463-2c05bd7f1ac6
|
||||
permissions:
|
||||
default:
|
||||
- bungeecord.command.server
|
||||
- bungeecord.command.list
|
||||
admin:
|
||||
- bungeecord.command.alert
|
||||
- bungeecord.command.end
|
||||
- bungeecord.command.ip
|
||||
- bungeecord.command.reload
|
||||
servers:
|
||||
s1:
|
||||
motd: SERVER 1
|
||||
address: localhost:25565
|
||||
restricted: false
|
||||
s2:
|
||||
motd: SERVER 2
|
||||
address: localhost:25566
|
||||
restricted: false
|
||||
player_limit: -1
|
||||
@@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
modules:
|
||||
- jenkins://cmd_alert
|
||||
- jenkins://cmd_find
|
||||
- jenkins://cmd_list
|
||||
- jenkins://cmd_send
|
||||
- jenkins://cmd_server
|
||||
- jenkins://reconnect_yaml
|
||||
@@ -0,0 +1 @@
|
||||
rsa/
|
||||
@@ -0,0 +1,74 @@
|
||||
# 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: false
|
||||
|
||||
# All tokens, labeled by the serviceName of each server list.
|
||||
tokens:
|
||||
# Default token for all server lists, if another isn't supplied.
|
||||
default: jqpli3gnd8l67qsg2d81vje09h
|
||||
|
||||
# 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
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../../universal/build/libs/nuvotifier.jar
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../resources/proxy.jar
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
cd $DIR
|
||||
|
||||
java -jar proxy.jar
|
||||
@@ -0,0 +1,8 @@
|
||||
use_netty_dns_resolver: true
|
||||
disable_modern_tab_limiter: true
|
||||
log_initial_handler_connections: true
|
||||
throttling:
|
||||
tab_complete: 1000
|
||||
game_version: ''
|
||||
log_server_list_ping: false
|
||||
allow_empty_packets: false
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,19 @@
|
||||
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/
|
||||
@@ -0,0 +1,37 @@
|
||||
# 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:
|
||||
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:
|
||||
monsters: 70
|
||||
animals: 10
|
||||
water-animals: 15
|
||||
ambient: 15
|
||||
chunk-gc:
|
||||
period-in-ticks: 600
|
||||
load-threshold: 0
|
||||
ticks-per:
|
||||
animal-spawns: 400
|
||||
monster-spawns: 1
|
||||
autosave: 6000
|
||||
aliases: now-in-commands.yml
|
||||
@@ -0,0 +1,17 @@
|
||||
# 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-
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../resources/paper.jar
|
||||
@@ -0,0 +1,178 @@
|
||||
# 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: 16
|
||||
settings:
|
||||
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
|
||||
generation: true
|
||||
thread-per-world-generation: true
|
||||
load-threads: -1
|
||||
use-alternative-luck-formula: false
|
||||
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:
|
||||
connection-throttle: Connection throttled! Please wait before reconnecting.
|
||||
flying-player: Flying is not enabled on this server
|
||||
flying-vehicle: Flying is not enabled on this server
|
||||
authentication-servers-down: ''
|
||||
timings:
|
||||
enabled: true
|
||||
verbose: true
|
||||
server-name-privacy: false
|
||||
hidden-config-entries:
|
||||
- database
|
||||
- settings.bungeecord-addresses
|
||||
history-interval: 300
|
||||
history-length: 3600
|
||||
world-settings:
|
||||
default:
|
||||
game-mechanics:
|
||||
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
|
||||
remove-corrupt-tile-entities: false
|
||||
queue-light-updates: false
|
||||
portal-search-radius: 128
|
||||
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
|
||||
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
|
||||
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
|
||||
- lit_redstone_ore
|
||||
- clay
|
||||
- emerald_ore
|
||||
- ender_chest
|
||||
replacement-blocks:
|
||||
- stone
|
||||
- planks
|
||||
skip-entity-ticking-in-chunks-scheduled-for-unload: true
|
||||
armor-stands-do-collision-entity-lookups: true
|
||||
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
|
||||
parrots-are-unaffected-by-player-movement: false
|
||||
prevent-tnt-from-moving-in-water: false
|
||||
non-player-arrow-despawn-rate: -1
|
||||
creative-arrow-despawn-rate: -1
|
||||
disable-explosion-knockback: false
|
||||
armor-stands-tick: true
|
||||
spawner-nerfed-mobs-should-jump: false
|
||||
baby-zombie-movement-speed: 0.5
|
||||
allow-leashing-undead-horse: false
|
||||
all-chunks-are-slime-chunks: false
|
||||
mob-spawner-tick-rate: 1
|
||||
squid-spawn-height:
|
||||
maximum: 0.0
|
||||
@@ -0,0 +1 @@
|
||||
rsa/
|
||||
@@ -0,0 +1,29 @@
|
||||
# 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
|
||||
|
||||
# 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
|
||||
@@ -0,0 +1,7 @@
|
||||
# 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
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../../universal/build/libs/nuvotifier.jar
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../s-run.sh
|
||||
@@ -0,0 +1,39 @@
|
||||
#Minecraft server properties
|
||||
#Fri Dec 21 00:58:41 EST 2018
|
||||
generator-settings=
|
||||
force-gamemode=false
|
||||
allow-nether=true
|
||||
enforce-whitelist=false
|
||||
gamemode=0
|
||||
broadcast-console-to-ops=true
|
||||
enable-query=false
|
||||
player-idle-timeout=0
|
||||
difficulty=1
|
||||
spawn-monsters=true
|
||||
op-permission-level=4
|
||||
pvp=true
|
||||
snooper-enabled=true
|
||||
level-type=DEFAULT
|
||||
hardcore=false
|
||||
enable-command-block=false
|
||||
max-players=20
|
||||
network-compression-threshold=256
|
||||
resource-pack-sha1=
|
||||
max-world-size=29999984
|
||||
server-port=25565
|
||||
server-ip=
|
||||
spawn-npcs=true
|
||||
allow-flight=false
|
||||
level-name=world
|
||||
view-distance=10
|
||||
resource-pack=
|
||||
spawn-animals=true
|
||||
white-list=false
|
||||
generate-structures=true
|
||||
online-mode=false
|
||||
max-build-height=256
|
||||
level-seed=
|
||||
prevent-proxy-connections=false
|
||||
use-native-transport=true
|
||||
motd=A Minecraft Server
|
||||
enable-rcon=false
|
||||
@@ -0,0 +1,128 @@
|
||||
# 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: 11
|
||||
settings:
|
||||
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
|
||||
debug: false
|
||||
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:
|
||||
verbose: true
|
||||
entity-tracking-range:
|
||||
players: 48
|
||||
animals: 48
|
||||
monsters: 48
|
||||
misc: 32
|
||||
other: 64
|
||||
ticks-per:
|
||||
hopper-transfer: 8
|
||||
hopper-check: 1
|
||||
hopper-amount: 1
|
||||
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
|
||||
merge-radius:
|
||||
exp: 3.0
|
||||
item: 2.5
|
||||
mob-spawn-range: 8
|
||||
growth:
|
||||
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:
|
||||
animals: 32
|
||||
monsters: 32
|
||||
misc: 16
|
||||
water: 16
|
||||
tick-inactive-villagers: true
|
||||
wither-spawn-sound-radius: 0
|
||||
enable-zombie-pigmen-portal-spawns: true
|
||||
item-despawn-rate: 6000
|
||||
view-distance: 10
|
||||
arrow-despawn-rate: 1200
|
||||
hanging-tick-frequency: 100
|
||||
zombie-aggressive-towards-villager: true
|
||||
nerf-spawner-mobs: false
|
||||
max-entity-collisions: 8
|
||||
@@ -0,0 +1,19 @@
|
||||
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/
|
||||
@@ -0,0 +1,37 @@
|
||||
# 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:
|
||||
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:
|
||||
monsters: 70
|
||||
animals: 10
|
||||
water-animals: 15
|
||||
ambient: 15
|
||||
chunk-gc:
|
||||
period-in-ticks: 600
|
||||
load-threshold: 0
|
||||
ticks-per:
|
||||
animal-spawns: 400
|
||||
monster-spawns: 1
|
||||
autosave: 6000
|
||||
aliases: now-in-commands.yml
|
||||
@@ -0,0 +1,17 @@
|
||||
# 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-
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../resources/paper.jar
|
||||
Loaded 100 of 143 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user