Compare commits

...
83 Commits
Author SHA1 Message Date
Stampede 4df84efdcd Version bump 2023-07-21 11:24:32 -05:00
Stampede 95fc2368a5 1.20.1 updates 2023-07-21 11:13:31 -05:00
Andrew Steinborn 9d69da2dab Demote errors in the fabric port 2023-05-13 23:04:41 -04:00
Andrew Steinborn 65007e248e Add basic Folia support
Untested, but should fix #321 since this is the only component of the plugin that explicitly demands the server thread
2023-05-13 23:03:49 -04:00
Andrew Steinborn f1f564d67a Merge pull request #322 from DrexHD/feature/fabric
Add fabric support
2023-05-13 22:57:03 -04:00
Drex f80de12ae2 Implement requested changes 2023-05-12 00:36:38 +02:00
Andrew Steinborn acb19a520a Merge pull request #309 from SeineEloquenz/bad_votes_warning
Make unprocessable votes a warning
2023-05-10 01:17:54 -04:00
Drex 9c27071213 Add fabric support 2023-05-07 17:11:39 +02:00
Alexander Linder 0f6020818d Make unprocessable votes a warning 2022-09-05 18:11:58 +02:00
Andrew Steinborn fcf6c93404 Merge pull request #296 from JOO200/feat/velocity3
Update to velocity 3, Make Proxy Events cancellable
2022-04-11 22:36:49 -04:00
JOO200 3d0f255ab4 BungeeCord: VotifierEvent is not Async anymore 2022-04-09 10:09:55 +02:00
JOO200 ede5cae333 Update to Velocity 3.1.0, make Events on Proxy cancellable 2022-04-09 10:05:13 +02:00
Andrew Steinborn b8f9cb937a Merge pull request #295 from JOO200/feat/update
Updated NuVotifier and Dependencies
2022-04-08 22:53:17 -04:00
JOO200 1fae11c0ff Fixed wrong plugin informations for sponge 2022-04-01 08:27:53 +02:00
JOO200 41eb6327e6 Cleanup tests 2022-03-30 09:47:46 +02:00
JOO200 c723fc7eac Set Source compatibility to Java 8 2022-03-30 09:42:33 +02:00
JOO200 7812823367 Fix wrong version in nuvotifier-bungee, added universal back 2022-03-30 09:29:01 +02:00
JOO200 2d5c2c78d2 Cleanup build script 2022-03-30 09:28:13 +02:00
JOO200 6e64890c04 Updated NuVotifier and Dependencies
* Updated Gradle to version 7.4.0
* Updated Java to version 17
* Moved to Gradle build scripts
* Fixed publishing maven artifacts
* Removed universal file (better depend on the specific implementations)
2022-03-30 09:27:55 +02:00
Andrew Steinborn d269e9da6b Back to 3.0.0-SNAPSHOT 2021-07-06 03:55:08 -04:00
Andrew Steinborn 5466876f41 NuVotifier 2.7.3 2021-07-06 03:49:40 -04:00
Andrew Steinborn 4003006fa4 Insert additional delay if no votes were evicted yet 2021-07-02 21:20:35 -04:00
Andrew Steinborn 13d0cb1d57 Merge pull request #256 from A248/gradle-7
Update to Gradle 7
2021-06-18 10:08:52 +00:00
Andrew Steinborn 15ad7d5abc Fix broken exception logging 2021-06-18 06:06:32 -04:00
A248 8966fbf3d8 Update to Gradle 7 with updates to shadowJar, blossom, and spotbugs 2021-06-11 12:08:35 -04:00
Andrew Steinborn 616d25683f Prepare for Velocity 3.0.0 support
To so do, drop Velocity 1.0.x support.
2021-05-23 23:10:59 -04:00
Andrew Steinborn 669c8a769e Await termination of event loop groups on shutdown.
May fix #241
2021-05-22 12:57:09 -04:00
Andrew Steinborn 7b10eca263 Back to 3.0.0-SNAPSHOT 2021-01-26 13:27:46 -05:00
Andrew Steinborn d22f4cd58b NuVotifier 2.7.2 2021-01-26 13:23:46 -05:00
Andrew Steinborn 8ba7e87067 Some extra logging 2021-01-06 13:27:28 -05:00
Andrew Steinborn 5776acaf34 Merge pull request #221 from Janmm14/relocate-gson
Relocate gson
2021-01-06 12:52:56 -05:00
Andrew Steinborn 90f6ed8b55 Merge pull request #222 from Eufranio/master
Prevent NPE when the fallbackServer is null
2021-01-06 12:47:13 -05:00
Eufranio 616bd4ad01 Prevent NPE when the fallbackServer is null 2021-01-06 14:44:44 -03:00
Janmm14 448baf0329 Relocate gson 2021-01-06 16:45:30 +01:00
Andrew Steinborn 13fe852bcd Update dev network to 1.16.2 2020-09-03 15:52:17 -04:00
Andrew Steinborn c996e36b97 Don't re-register the raw data channel if it's already registered. Fixes #206 2020-08-14 14:36:01 -04:00
Andrew Steinborn ad9e6f5fe8 Do proper version replacement in Sponge/Velocity plugins using blossom 2020-07-13 13:22:24 -04:00
Andrew Steinborn 53eb9c8fec Update test network to 1.16.1 2020-07-07 22:18:49 -04:00
Andrew Steinborn 6dbda167d3 We support Bukkit 1.7.10 too. 2020-07-06 02:54:02 -04:00
Andrew Steinborn be9f92d5cd Bump back to 3.0.0-SNAPSHOT 2020-06-25 15:17:52 -04:00
Andrew Steinborn 96bf715fbc Bump to NuVotifier 2.7.1 2020-06-25 15:15:28 -04:00
Andrew Steinborn 628273413b Don't assume all channels will be NioSocketChannels. Fixes #196 2020-06-25 15:06:46 -04:00
Andrew Steinborn bbfa9e4aa9 Back to 3.0.0-SNAPSHOT 2020-06-24 22:39:54 -04:00
Andrew Steinborn a34b07f2d1 NuVotifier 2.7.0 2020-06-24 22:27:51 -04:00
Andrew Steinborn 1d90a59d5a Add more ArgsToVote tests. 2020-06-23 23:45:30 -04:00
Andrew Steinborn 2734836e58 Add a Sponge server to the testbed and fix one Sponge-related problem 2020-06-23 19:46:26 -04:00
Andrew Steinborn 768daf099f Make Velocity NuVotifier reload command consistent 2020-06-23 19:12:03 -04:00
Andrew Steinborn b6292667b8 Add Velocity testbed and fix some bugs in the Velocity port 2020-06-23 19:05:14 -04:00
Andrew Steinborn 8519535566 Do some test modernization, add some basic ArgsToVote tests 2020-06-23 18:46:58 -04:00
Andrew Steinborn 08e907678f Add additional debug output for pluginMessage forwarding 2020-06-23 18:25:27 -04:00
Andrew Steinborn 27b93ac045 Update dev environment to 1.15 2020-06-16 08:44:50 -04:00
Andrew Steinborn 97f4c91c24 Integrate SpotBugs and fix all errors. 2020-06-06 02:13:29 -04:00
Andrew Steinborn e919c845f7 Attempt to fix vote cache TTL
Instead of trying to expose this data by API, it is now internal to the vote caches.

The only substantial API change was the introduction of a copy constructor to the Vote object.
2020-06-04 00:06:09 -04:00
Andrew Steinborn 1fb35276f7 Support native reloading on BungeeCord, Sponge, and Velocity
/nvreload will remain supported to provide a consistent reload command for documentation, but it is now mostly only relevant for BungeeCord only.
2020-06-03 23:49:02 -04:00
Andrew Steinborn f5f596f21c Document that the Sponge event is fired sync now. 2020-06-03 23:37:41 -04:00
Andrew Steinborn 88523db213 Update issue templates 2020-05-30 23:53:06 -04:00
Andrew Steinborn 3599b0fa1b Restore compatibility with plugins that create Vote objects manually 2020-05-30 18:09:49 -04:00
Andrew Steinborn 950b543e98 Allow using up to 3 worker threads for NuVotifier 2020-05-14 01:38:24 -04:00
Andrew Steinborn e66927dd1a Add epoll transport support 2020-05-14 01:36:40 -04:00
Andrew Steinborn 98d4f69ffb Throttle errors and do general Netty optimizations
Closes #170
2020-05-14 00:58:18 -04:00
Andrew Steinborn ad44c76309 Fix build issues on Windows by just copying the file and not using a symbolic link 2020-05-11 14:24:39 -04:00
Andrew Steinborn 58de66699e Upgrade to Gradle 6.4 and resolve all deprecation issues. 2020-05-11 14:07:25 -04:00
Andrew Steinborn b81052327d Remove almost all use of Guava, closes #187
Guava has proven problematic on a number of occasions - it has caused issues with the Sponge port and there are incompatibilities with certain Forge-Bukkit hybrids. As we desire maximum compatibility, it is better simply to drop Guava altogether. Java 8+ already implements much of what was the bread-and-butter of Guava, or makes rolling our own implementation simpler.

The one place where Guava has been retained is in some configuration-loading code specific to Sponge, which can't be changed to use a non-Guava alternative.
2020-05-11 13:28:43 -04:00
Andrew Steinborn 59a3eff421 Clean up a little Netty code. 2020-05-08 16:50:13 -04:00
Andrew Steinborn 411a529e18 Notify users if they don't have plugins available to listen for votifier events on Bukkit. 2020-05-08 16:39:03 -04:00
Andrew Steinborn 4d6946ab2c Fix a typo 2020-05-08 16:29:20 -04:00
Andrew Steinborn d5ae57b28e Bump Netty to 4.1.49.Final
This could resolve (or greatly ameliorate) #170 but unverified.
2020-04-22 11:47:12 -04:00
Joe Hirschfeld aa1d9b3d71 [api] Add additional data field to the vote 2019-12-08 12:59:09 -08:00
Joe Hirschfeld c250048bf7 Remove localTimestamp field from Vote 2019-10-11 21:49:35 -07:00
Joe Hirschfeld 1db78a5bea Create new api module
Creates a new API module with stable api interfaces. Adds some unit
tests to the vote test, updates unit testing framework across the board.
2019-10-11 21:35:58 -07:00
Joe Hirschfeld 2ad0fa118f Update repositories in build.gradle 2019-10-11 20:43:05 -07:00
Joseph Hirschfeld 38afe59db0 Remove discord support link 2019-10-02 21:18:30 -07:00
Joseph Hirschfeld b173930c14 Add some much needed newlines! 2019-06-29 22:50:54 -07:00
Joseph Hirschfeld 61c4529df6 Update readme 2019-06-29 22:49:58 -07:00
Joseph Hirschfeld dd48af5b50 Move docs to github wiki page 2019-06-29 22:33:41 -07:00
Glare bc3c667d2a Fix a typo in the FAQ (#148) 2019-06-28 22:27:33 -07:00
Joseph Hirschfeld 1041bdb6b7 Add paypal donation link 2019-06-22 10:46:29 -04:00
Joseph Hirschfeld fc7a04547c Use bukkit ChatColor, not md5 ChatColor 2019-06-19 01:24:04 -04:00
Joseph Hirschfeld dd2a1c4ed5 Add /(p)testvote command 2019-06-19 01:20:07 -04:00
Joseph Hirschfeld c7667abf24 Remove bad singleton pattern use 2019-06-19 00:26:34 -04:00
Joseph Hirschfeld df484523c2 Add /nv(p)reload to all platforms
Also extract all existing platforms into a more modular command
interface
2019-06-19 00:23:14 -04:00
Joseph Hirschfeld 81fca5b218 Only expose the remote address string to handlers 2019-06-18 23:29:30 -04:00
Joseph Hirschfeld c520fd6650 Assume new cache when cachefile is empty 2019-06-17 10:33:46 -04:00
163 changed files with 4193 additions and 1792 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
custom: https://www.paypal.me/ibj
+73
View File
@@ -0,0 +1,73 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
<!--
Thank you for submitting an issue for NuVotifier. We appreciate
any and all bug reports.
Before you continue, please ensure you are reporting
a real bug and are not suffering from a configuration issue
or a plugin problem. Once NuVotifier has received and
called your vote listeners, it's not our issue any more.
If you are having difficulty setting up NuVotifier to receive
votes, you are in the wrong place. Please view our wiki
at https://github.com/NuVotifier/NuVotifier/wiki for a
comprehensive set of instructions for configuring and
diagnosing common problems with NuVotifier.
If you are certain that there is a problem with
NuVotifier, please continue filling the form below.
Make sure to follow these instructions:
1. Tell the exact NuVotifier version you're using.
If you just say "latest" your issue will get closed.
2. Please include all relevant logs and configuration
files. Make sure to redact any tokens in the config
file.
3. Don't write anything between these arrows.
We won't see it otherwise.
4. Before you create your issue, delete this block of
text and the code block at the very bottom. -->
### Information
**Platform on which you run NuVotifier**: <!-- Either Bukkit, Sponge, BungeeCord, or Velocity. -->
**Version of NuVotifier**:
```
<!--
Getting this depends on your platform:
Bukkit: Use /version Votifier
Sponge: Use /sponge plugins and find NuVotifier
BungeeCord: No such command exists.
Velocity: Use /velocity plugins from in-game and hover over "nuvotifier"
-->
```
**Relevant log**: <!-- Paste the log on https://gist.github.com/, replace this with link -->
### Details
**Description**
<!-- Replace this with a brief summary of the bug. -->
**Steps to reproduce**
<!-- Replace this with exactly what you did to cause the bug. -->
**Expected behavior**
<!-- Replace this with what you expected to happen. -->
```
Please make sure to delete this line of text and the three ``` characters surrounding it, or your issue will be closed.
```
+3
View File
@@ -47,3 +47,6 @@ debug.txt
# gradle stuff
.gradle/
.gradletasknamecache
# fabric
fabric/run/
+14 -24
View File
@@ -1,35 +1,25 @@
# 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.
NuVotifier is a secure alternative to using the original Votifier project.
NuVotifier will work in place of Votifier - any vote listener that supports
Votifier will also support NuVotifier.
## Why fork Votifier?
NuVotifier supports many different platforms using the same universal JAR:
Votifier has lagged in activity, has severe security flaws in its protocol, and
is vulnerable to denial of service attacks.
+ Bukkit / Spigot / Paper >=1.7.10
+ Sponge 7
+ BungeeCord / Waterfall
+ Velocity
## Is NuVotifier a drop-in replacement for Votifier?
NuVotifier also adds forwarding and listener test commands not present in the
original version.
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.
[Setup Guide](https://github.com/NuVotifier/NuVotifier/wiki/Setup-Guide)
# Documentation / Configuration Guide
[Troubleshooting Guide](https://github.com/NuVotifier/NuVotifier/wiki/Troubleshooting-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:
[![Discord](https://i.imgur.com/HLPoNnY.png)](https://discord.gg/anUtuAC)
[Developer Information](https://github.com/NuVotifier/NuVotifier/wiki/Developer-Documentation)
# License
NuVotifier is GNU GPLv3 licensed. This project's license can be viewed
[here](LICENSE).
NuVotifier is GNU GPLv3 licensed. This project's license can be viewed [here](LICENSE).
+4
View File
@@ -0,0 +1,4 @@
# nuvotifier-api
Files in this directory have a strong API promise - they really shouldn't
change over time. Feel free to depend against it!
+10
View File
@@ -0,0 +1,10 @@
plugins {
`java-library`
}
applyPlatformAndCoreConfiguration()
applyCommonArtifactoryConfig()
dependencies {
"implementation"("com.google.code.gson:gson:${Versions.GSON}")
}
@@ -21,6 +21,9 @@ package com.vexsoftware.votifier.model;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.Arrays;
import java.util.Base64;
/**
* A model for a vote.
*
@@ -48,26 +51,31 @@ public class Vote {
*/
private String timeStamp;
/**
* Timestamp (unix-millis) normalized and taken from a known source
*/
private final long localTimestamp;
private byte[] additionalData;
@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;
this.additionalData = null;
}
public Vote(String serviceName, String username, String address, String timeStamp, byte[] additionalData) {
this.serviceName = serviceName;
this.username = username;
this.address = address;
this.timeStamp = timeStamp;
this.additionalData = additionalData == null ? null : additionalData.clone();
}
public Vote(Vote vote) {
this(vote.getServiceName(), vote.getUsername(), vote.getAddress(), vote.getTimeStamp(),
vote.getAdditionalData() == null ? null : vote.getAdditionalData().clone());
}
private static String getTimestamp(JsonElement object) {
@@ -82,16 +90,22 @@ public class Vote {
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()));
getTimestamp(jsonObject.get("timestamp")));
if (jsonObject.has("additionalData"))
additionalData = Base64.getDecoder().decode(jsonObject.get("additionalData").getAsString());
}
@Override
public String toString() {
String data;
if (additionalData == null)
data = "null";
else
data = Base64.getEncoder().encodeToString(additionalData);
return "Vote (from:" + serviceName + " username:" + username
+ " address:" + address + " timeStamp:" + timeStamp
+ " localTimestamp:" + localTimestamp + ")";
+ " additionalData:" + data + ")";
}
/**
@@ -171,12 +185,12 @@ public class Vote {
}
/**
* Gets the local timestamp, in unix-millis. Calculated locally by a NuVotifier instance
* Returns additional data sent with the vote, if it exists.
*
* @return The local timestamp
* @return Additional data sent with the vote
*/
public long getLocalTimestamp() {
return localTimestamp;
public byte[] getAdditionalData() {
return additionalData == null ? null : additionalData.clone();
}
public JsonObject serialize() {
@@ -185,21 +199,23 @@ public class Vote {
ret.addProperty("username", username);
ret.addProperty("address", address);
ret.addProperty("timestamp", timeStamp);
ret.addProperty("localTimestamp", localTimestamp);
if (additionalData != null)
ret.addProperty("additionalData", Base64.getEncoder().encodeToString(additionalData));
return ret;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!(o instanceof Vote)) return false;
Vote vote = (Vote) o;
if (!serviceName.equals(vote.serviceName)) return false;
if (!username.equals(vote.username)) return false;
if (!address.equals(vote.address)) return false;
return timeStamp.equals(vote.timeStamp);
if (!timeStamp.equals(vote.timeStamp)) return false;
return Arrays.equals(additionalData, vote.additionalData);
}
@Override
@@ -0,0 +1,77 @@
package com.vexsoftware.votifier.model;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class VoteTest {
@Test
public void testVoteConstructFromJsonObject() {
String serviceName = "serviceNameA";
String username = "usernameA";
String address = "home";
String timestamp = "1234";
JsonObject o = new JsonObject();
o.addProperty("serviceName", serviceName);
o.addProperty("username", username);
o.addProperty("address", address);
o.addProperty("timestamp", timestamp);
Vote v = new Vote(o);
assertEquals(serviceName, v.getServiceName());
assertEquals(username, v.getUsername());
assertEquals(address, v.getAddress());
assertEquals(timestamp, v.getTimeStamp());
}
@Test
public void testVoteConstructFromJsonObjectLongTS() {
String serviceName = "serviceNameA";
String username = "usernameA";
String address = "home";
JsonObject o = new JsonObject();
o.addProperty("serviceName", serviceName);
o.addProperty("username", username);
o.addProperty("address", address);
o.addProperty("timestamp", 1234);
Vote v = new Vote(o);
assertEquals(serviceName, v.getServiceName());
assertEquals(username, v.getUsername());
assertEquals(address, v.getAddress());
assertEquals("1234", v.getTimeStamp());
}
@Test
public void testVoteSerialize() {
Vote v = new Vote("service", "username", "address", "1234");
JsonObject o = v.serialize();
assertEquals(4, o.size());
assertEquals("service", o.get("serviceName").getAsString());
assertEquals("username", o.get("username").getAsString());
assertEquals("address", o.get("address").getAsString());
assertEquals("1234", o.get("timestamp").getAsString());
}
@Test
public void testVoteEquals() {
Vote v1 = new Vote("service", "username", "address", "1234");
assertEquals(v1, v1);
assertEquals(v1.hashCode(), new Vote("service", "username", "address", "1234").hashCode());
assertEquals(v1, new Vote("service", "username", "address", "1234"));
assertNotEquals(v1, new Object());
assertNotEquals(v1, null);
assertNotEquals(v1, new Vote("service2", "username", "address", "1234"));
assertNotEquals(v1, new Vote("service", "username1", "address", "1234"));
assertNotEquals(v1, new Vote("service", "username", "address1", "1234"));
assertNotEquals(v1, new Vote("service", "username", "address", "12345"));
}
}
-28
View File
@@ -1,28 +0,0 @@
import org.apache.tools.ant.filters.ReplaceTokens
allprojects {
group 'com.vexsoftware'
version '3.0.0-SNAPSHOT'
}
subprojects {
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven {
url 'https://nexus.ibj.io/repository/maven-public/'
}
}
processResources {
filter(ReplaceTokens, tokens: ["app.version": this.project.version])
}
}
if (System.env.PUBLISH) {
apply from: 'publish.gradle'
}
+31
View File
@@ -0,0 +1,31 @@
import org.ajoberstar.grgit.Grgit
plugins {
id("net.kyori.blossom") version "1.2.0" apply false
}
logger.lifecycle("""
*******************************************
You are building NuVotifier!
If you encounter trouble:
1) Try running 'build' in a separate Gradle run
2) Use gradlew and not gradle
3) If you still need help, you should reconsider building NuVotifier!
Output files will be in [subproject]/build/libs
*******************************************
""")
applyRootArtifactoryConfig()
if (!project.hasProperty("gitCommitHash")) {
apply(plugin = "org.ajoberstar.grgit")
ext["gitCommitHash"] = try {
Grgit.open(mapOf("currentDir" to project.rootDir))?.head()?.abbreviatedId
} catch (e: Exception) {
logger.warn("Error getting commit hash", e)
"no.git.id"
}
}
+26
View File
@@ -0,0 +1,26 @@
plugins {
`kotlin-dsl`
kotlin("jvm") version embeddedKotlinVersion
}
repositories {
mavenCentral()
gradlePluginPortal()
maven {
name = "sponge"
url = uri("https://repo.spongepowered.org/repository/maven-public/")
}
maven {
name = "fabric"
url = uri("https://maven.fabricmc.net/")
}
}
dependencies {
implementation(gradleApi())
implementation("org.ajoberstar.grgit:grgit-gradle:4.1.1")
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2")
implementation("org.jfrog.buildinfo:build-info-extractor-gradle:4.27.1")
implementation("org.spongepowered:spongegradle-plugin-development:2.0.0")
implementation("net.fabricmc:fabric-loom:1.0-SNAPSHOT")
}
@@ -0,0 +1,56 @@
import org.gradle.api.Project
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.kotlin.dsl.*
import org.jfrog.gradle.plugin.artifactory.dsl.ArtifactoryPluginConvention
import org.jfrog.gradle.plugin.artifactory.task.ArtifactoryTask
private const val ARTIFACTORY_CONTEXT_URL = "artifactory_contextUrl"
private const val ARTIFACTORY_USER = "artifactory_user"
private const val ARTIFACTORY_PASSWORD = "artifactory_password"
fun Project.applyRootArtifactoryConfig() {
if (!project.hasProperty(ARTIFACTORY_CONTEXT_URL)) ext[ARTIFACTORY_CONTEXT_URL] = "http://localhost"
if (!project.hasProperty(ARTIFACTORY_USER)) ext[ARTIFACTORY_USER] = "guest"
if (!project.hasProperty(ARTIFACTORY_PASSWORD)) ext[ARTIFACTORY_PASSWORD] = ""
apply(plugin = "com.jfrog.artifactory")
configure<ArtifactoryPluginConvention> {
setContextUrl("${project.property(ARTIFACTORY_CONTEXT_URL)}")
clientConfig.publisher.run {
repoKey = when {
"${project.version}".contains("SNAPSHOT") -> "private"
else -> "public"
}
username = "${project.property(ARTIFACTORY_USER)}"
password = "${project.property(ARTIFACTORY_PASSWORD)}"
isMaven = true
isIvy = false
}
}
tasks.named<ArtifactoryTask>("artifactoryPublish") {
isSkip = true
}
}
fun Project.applyCommonArtifactoryConfig() {
configure<PublishingExtension> {
publications {
register<MavenPublication>("maven") {
from(components["java"])
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
}
}
}
tasks.named<ArtifactoryTask>("artifactoryPublish") {
publications("maven")
}
}
+33
View File
@@ -0,0 +1,33 @@
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.repositories
import org.gradle.kotlin.dsl.the
fun Project.applyCommonConfiguration() {
group = rootProject.group
version = rootProject.version
repositories {
mavenCentral()
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") }
maven {
name = "Fabric"
url = uri("https://maven.fabricmc.net/")
}
}
configurations.all {
resolutionStrategy {
cacheChangingModulesFor(5, "MINUTES")
}
}
plugins.withId("java") {
the<JavaPluginExtension>().toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
}
@@ -0,0 +1,75 @@
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.api.tasks.testing.Test
import org.gradle.external.javadoc.StandardJavadocDocletOptions
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.withType
fun Project.applyCommonJavaConfiguration(sourcesJar: Boolean, javaRelease: Int = 8, banSlf4j: Boolean = true) {
applyCommonConfiguration()
apply(plugin = "eclipse")
apply(plugin = "idea")
tasks
.withType<JavaCompile>()
.matching { it.name == "compileJava" || it.name == "compileTestJava" }
.configureEach {
val disabledLint = listOf(
"processing", "path", "fallthrough", "serial"
)
options.release.set(javaRelease)
options.compilerArgs.addAll(listOf("-Xlint:all") + disabledLint.map { "-Xlint:-$it" })
options.isDeprecation = true
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
dependencies {
"compileOnly"("com.google.code.findbugs:jsr305:${Versions.FINDBUGS}")
"testImplementation"("org.junit.jupiter:junit-jupiter-api:${Versions.JUNIT}")
"testImplementation"("org.junit.jupiter:junit-jupiter-params:${Versions.JUNIT}")
"testImplementation"("org.mockito:mockito-core:${Versions.MOCKITO}")
"testImplementation"("org.mockito:mockito-junit-jupiter:${Versions.MOCKITO}")
"testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:${Versions.JUNIT}")
}
// Java 8 turns on doclint which we fail
tasks.withType<Javadoc>().configureEach {
options.encoding = "UTF-8"
(options as StandardJavadocDocletOptions).apply {
addStringOption("Xdoclint:none", "-quiet")
tags(
"apiNote:a:API Note:",
"implSpec:a:Implementation Requirements:",
"implNote:a:Implementation Note:"
)
}
}
configure<JavaPluginExtension> {
disableAutoTargetJvm()
withJavadocJar()
if (sourcesJar) {
withSourcesJar()
}
}
if (banSlf4j) {
configurations["compileClasspath"].apply {
resolutionStrategy.componentSelection {
withModule("org.slf4j:slf4j-api") {
reject("No SLF4J allowed on compile classpath")
}
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
import org.gradle.api.Project
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.the
val Project.ext: ExtraPropertiesExtension
get() = extensions.getByType()
val Project.sourceSets: SourceSetContainer
get() = the<JavaPluginExtension>().sourceSets
@@ -0,0 +1,42 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.Project
import org.gradle.api.component.AdhocComponentWithVariants
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.named
fun Project.applyPlatformAndCoreConfiguration(javaRelease: Int = 8) {
applyCommonConfiguration()
apply(plugin = "java")
apply(plugin = "maven-publish")
apply(plugin = "com.jfrog.artifactory")
applyCommonJavaConfiguration(
sourcesJar = true,
javaRelease = javaRelease,
banSlf4j = false
)
ext["internalVersion"] = "$version+${rootProject.ext["gitCommitHash"]}"
}
fun Project.applyShadowConfiguration() {
apply(plugin = "com.github.johnrengelman.shadow")
tasks.named<ShadowJar>("shadowJar") {
archiveClassifier.set("dist")
dependencies {
include(project(":nuvotifier-api"))
include(project(":nuvotifier-common"))
exclude("com.google.code.findbugs:jsr305")
}
exclude("GradleStart**")
exclude(".cache")
exclude("LICENSE*")
exclude("META-INF/maven/**")
}
val javaComponent = components["java"] as AdhocComponentWithVariants
// I don't think we want this published (it's the shadow jar)
javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
skip()
}
}
+8
View File
@@ -0,0 +1,8 @@
object Versions {
const val JUNIT = "5.8.1"
const val MOCKITO = "4.3.1"
const val FINDBUGS = "3.0.2"
const val NETTYIO = "4.1.72.Final"
const val GSON = "2.9.0"
const val VELOCITY = "3.1.0"
}
-5
View File
@@ -1,5 +0,0 @@
dependencies {
compileOnly group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
compile project(":nuvotifier-common")
}
+56
View File
@@ -0,0 +1,56 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
`java-library`
}
applyPlatformAndCoreConfiguration()
applyCommonArtifactoryConfig()
applyShadowConfiguration()
repositories {
maven {
name = "paper"
url = uri("https://papermc.io/repo/repository/maven-public/")
}
}
configurations {
compileClasspath.get().extendsFrom(create("shadeOnly"))
}
dependencies {
"compileOnly"("io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT")
"api"(project(":nuvotifier-api"))
"api"(project(":nuvotifier-common"))
}
tasks.named<Copy>("processResources") {
val internalVersion = project.ext["internalVersion"]
inputs.property("internalVersion", internalVersion)
filesMatching("plugin.yml") {
expand("internalVersion" to internalVersion)
}
}
tasks.named<Jar>("jar") {
val projectVersion = project.version
inputs.property("projectVersion", projectVersion)
manifest {
attributes("Implementation-Version" to projectVersion)
}
}
tasks.named<ShadowJar>("shadowJar") {
configurations = listOf(project.configurations["shadeOnly"], project.configurations["runtimeClasspath"])
dependencies {
include(dependency(":nuvotifier-api"))
include(dependency(":nuvotifier-common"))
}
}
tasks.named("assemble").configure {
dependsOn("shadowJar")
}
@@ -17,21 +17,6 @@ class BukkitScheduler implements VotifierScheduler {
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)));
@@ -18,8 +18,8 @@
package com.vexsoftware.votifier;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import com.vexsoftware.votifier.cmd.NVReloadCmd;
import com.vexsoftware.votifier.cmd.TestVoteCmd;
import com.vexsoftware.votifier.forwarding.BukkitPluginMessagingForwardingSink;
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSink;
@@ -33,20 +33,20 @@ import com.vexsoftware.votifier.platform.JavaUtilLogger;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import com.vexsoftware.votifier.util.IOUtil;
import com.vexsoftware.votifier.util.KeyCreator;
import com.vexsoftware.votifier.util.TokenUtil;
import io.netty.channel.Channel;
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.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.Key;
import java.security.KeyPair;
import java.util.HashMap;
@@ -61,16 +61,6 @@ import java.util.logging.Level;
*/
public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, VotifierPlugin, ForwardedVoteListener {
/**
* The Votifier instance.
*/
private static NuVotifierBukkit instance;
/**
* The current Votifier version.
*/
private String version;
/**
* The server bootstrap.
*/
@@ -94,12 +84,24 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
private ForwardingVoteSink forwardingMethod;
private VotifierScheduler scheduler;
private LoggingAdapter pluginLogger;
private boolean isFolia;
private boolean loadAndBind() {
try {
Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler");
isFolia = true;
getLogger().info("Using Folia; VotifierEvent will be fired asynchronously.");
} catch (ClassNotFoundException e) {
isFolia = false;
}
scheduler = new BukkitScheduler(this);
pluginLogger = new JavaUtilLogger(getLogger());
if (!getDataFolder().exists()) {
getDataFolder().mkdir();
if (!getDataFolder().mkdir()) {
throw new RuntimeException("Unable to create the plugin data folder " + getDataFolder());
}
}
// Handle configuration.
@@ -124,13 +126,15 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
getLogger().info("Configuring Votifier for the first time...");
// Initialize the configuration file.
config.createNewFile();
if (!config.createNewFile()) {
throw new IOException("Unable to create the config file at " + config);
}
// Load and manually replace variables in the configuration.
String cfgStr = new String(ByteStreams.toByteArray(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
String cfgStr = new String(IOUtil.readAllBytes(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
String token = TokenUtil.newToken();
cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
Files.asCharSink(config, StandardCharsets.UTF_8).write(cfgStr);
Files.copy(new ByteArrayInputStream(cfgStr.getBytes(StandardCharsets.UTF_8)), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
/*
* Remind hosted server admins to be sure they have the right
@@ -164,7 +168,9 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
*/
try {
if (!rsaDirectory.exists()) {
rsaDirectory.mkdir();
if (!rsaDirectory.mkdir()) {
throw new RuntimeException("Unable to create the RSA key folder " + rsaDirectory);
}
keyPair = RSAKeygen.generate(2048);
RSAIO.save(rsaDirectory, keyPair);
} else {
@@ -273,10 +279,8 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
@Override
public void onEnable() {
NuVotifierBukkit.instance = this;
// Set the plugin version.
version = getDescription().getVersion();
getCommand("nvreload").setExecutor(new NVReloadCmd(this));
getCommand("testvote").setExecutor(new TestVoteCmd(this));
if (!loadAndBind()) {
gracefulExit();
@@ -290,17 +294,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
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() {
public boolean reload() {
try {
halt();
} catch (Exception ex) {
@@ -309,6 +303,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
if (loadAndBind()) {
getLogger().info("Reload was successful.");
return true;
} else {
try {
halt();
@@ -316,6 +311,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
} catch (Exception ex) {
getLogger().log(Level.SEVERE, "On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!", ex);
}
return false;
}
}
@@ -323,15 +319,6 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
getLogger().log(Level.SEVERE, "Votifier did not initialize properly!");
}
/**
* Gets the instance.
*
* @return The instance
*/
public static NuVotifierBukkit getInstance() {
return instance;
}
@Override
public LoggingAdapter getPluginLogger() {
return pluginLogger;
@@ -357,28 +344,24 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
}
@Override
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) {
if (debug) {
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
} else {
getLogger().info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
}
getLogger().info("Got a " + protocolVersion.humanReadable + " vote record from " + remoteAddress + " -> " + vote);
}
Bukkit.getScheduler().runTask(this, () -> Bukkit.getPluginManager().callEvent(new VotifierEvent(vote)));
fireVotifierEvent(vote);
}
@Override
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
if (debug) {
if (alreadyHandledVote) {
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
"occurred with a vote from " + channel.remoteAddress(), throwable);
getLogger().log(Level.WARNING, "Vote processed, however an exception " +
"occurred with a vote from " + remoteAddress, throwable);
} else {
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
getLogger().log(Level.WARNING, "Unable to process vote from " + remoteAddress, throwable);
}
} else if (!alreadyHandledVote) {
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
getLogger().log(Level.WARNING, "Unable to process vote from " + remoteAddress);
}
}
@@ -387,6 +370,24 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
if (debug) {
getLogger().info("Got a forwarded vote -> " + v);
}
Bukkit.getScheduler().runTask(this, () -> Bukkit.getPluginManager().callEvent(new VotifierEvent(v)));
fireVotifierEvent(v);
}
private void fireVotifierEvent(Vote vote) {
if (VotifierEvent.getHandlerList().getRegisteredListeners().length == 0) {
getLogger().log(Level.SEVERE, "A vote was received, but you don't have any listeners available to listen for it.");
getLogger().log(Level.SEVERE, "See https://github.com/NuVotifier/NuVotifier/wiki/Setup-Guide#vote-listeners for");
getLogger().log(Level.SEVERE, "a list of listeners you can configure.");
}
if (!isFolia) {
getServer().getScheduler().runTask(
this, () -> getServer().getPluginManager().callEvent(new VotifierEvent(vote))
);
} else {
getServer().getScheduler().runTaskAsynchronously(
this, () -> getServer().getPluginManager().callEvent(new VotifierEvent(vote, true))
);
}
}
}
@@ -0,0 +1,31 @@
package com.vexsoftware.votifier.cmd;
import com.vexsoftware.votifier.NuVotifierBukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class NVReloadCmd implements CommandExecutor {
private final NuVotifierBukkit plugin;
public NVReloadCmd(NuVotifierBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender.hasPermission("nuvotifier.reload")) {
sender.sendMessage(ChatColor.GRAY + "Reloading NuVotifier...");
if (plugin.reload()) {
sender.sendMessage(ChatColor.DARK_GREEN + "NuVotifier has been reloaded!");
} else {
sender.sendMessage(ChatColor.DARK_RED + "Looks like there was a problem reloading NuVotifier, check the console!");
}
} else {
sender.sendMessage(ChatColor.DARK_RED + "You do not have permission to do this!");
}
return true;
}
}
@@ -0,0 +1,40 @@
package com.vexsoftware.votifier.cmd;
import com.vexsoftware.votifier.NuVotifierBukkit;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.ArgsToVote;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class TestVoteCmd implements CommandExecutor {
private final NuVotifierBukkit plugin;
public TestVoteCmd(NuVotifierBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender.hasPermission("nuvotifier.testvote")) {
Vote v;
try {
v = ArgsToVote.parse(args);
} catch (IllegalArgumentException e) {
sender.sendMessage(ChatColor.DARK_RED + "Error while parsing arguments to create test vote: " + e.getMessage());
sender.sendMessage(ChatColor.GRAY + "Usage hint: /testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]");
return true;
}
plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
sender.sendMessage(ChatColor.GREEN + "Test vote executed: " + v.toString());
} else {
sender.sendMessage(ChatColor.DARK_RED + "You do not have permission to do this!");
}
return true;
}
}
@@ -37,7 +37,7 @@ public class BukkitPluginMessagingForwardingSink extends AbstractPluginMessaging
try {
this.handlePluginMessage(bytes);
} catch (Exception e) {
NuVotifierBukkit.getInstance().getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
p.getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
}
}
}
@@ -5,7 +5,7 @@ import org.bukkit.event.HandlerList;
/**
* {@code VotifierEvent} is a custom Bukkit event class that is sent
* synchronously to CraftBukkit's main thread allowing other plugins to listener
* synchronously to CraftBukkit's main thread allowing other plugins to listen
* for votes.
*
* @author frelling
@@ -30,6 +30,17 @@ public class VotifierEvent extends Event {
this.vote = vote;
}
/**
* Constructs a vote event that encapsulated the given vote record.
*
* @param vote vote record
* @param async whether the event will be fired asynchronously
*/
public VotifierEvent(final Vote vote, final boolean async) {
super(async);
this.vote = vote;
}
/**
* Return the encapsulated vote record.
*
+16 -3
View File
@@ -1,6 +1,6 @@
name: Votifier
main: com.vexsoftware.votifier.NuVotifierBukkit
version: @app.version@
version: "${internalVersion}"
description: A plugin that gets notified when votes are made for the server on toplists.
authors: [Ichbinjoe, blakeman8192, Kramer, tuxed]
api-version: 1.13
@@ -8,6 +8,19 @@ api-version: 1.13
commands:
nvreload:
description: Reloads the NuVotifier configuration
permission: nuvotifier.console
permission: nuvotifier.reload
permission-message: "You do not have permission to run this command."
usage: "/nvreload"
usage: "/nvreload"
testvote:
description: Sends a test vote to the server
permission: nuvotifier.testvote
permission-message: "You do not have permission to run this command."
usage: "/testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]"
permissions:
nuvotifier.reload:
description: Allows you to reload the NuVotifier plugin
default: op
nuvotifier.testvote:
description: Allows you to send a test vote
default: op
-4
View File
@@ -1,4 +0,0 @@
dependencies {
compileOnly group: "net.md-5", name: "bungeecord-api", version: "1.8-SNAPSHOT"
compile project(":nuvotifier-common")
}
+56
View File
@@ -0,0 +1,56 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
`java-library`
}
applyPlatformAndCoreConfiguration()
applyCommonArtifactoryConfig()
applyShadowConfiguration()
repositories {
maven {
name = "bungeecord"
url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
}
}
configurations {
compileClasspath.get().extendsFrom(create("shadeOnly"))
}
dependencies {
"compileOnly"("net.md-5:bungeecord-api:1.18-R0.1-SNAPSHOT")
"api"(project(":nuvotifier-api"))
"api"(project(":nuvotifier-common"))
}
tasks.named<Copy>("processResources") {
val internalVersion = project.ext["internalVersion"]
inputs.property("internalVersion", internalVersion)
filesMatching("bungee.yml") {
expand("internalVersion" to internalVersion)
}
}
tasks.named<Jar>("jar") {
val projectVersion = project.version
inputs.property("projectVersion", projectVersion)
manifest {
attributes("Implementation-Version" to projectVersion)
}
}
tasks.named<ShadowJar>("shadowJar") {
configurations = listOf(project.configurations["shadeOnly"], project.configurations["runtimeClasspath"])
dependencies {
include(dependency(":nuvotifier-api"))
include(dependency(":nuvotifier-common"))
}
}
tasks.named("assemble").configure {
dependsOn("shadowJar")
}
@@ -1,14 +1,15 @@
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;
import java.util.Objects;
public class BungeeBackendServer implements BackendServer {
private final ServerInfo info;
public BungeeBackendServer(ServerInfo info) {
this.info = Preconditions.checkNotNull(info, "info");
this.info = Objects.requireNonNull(info, "info");
}
@Override
@@ -13,21 +13,6 @@ class BungeeScheduler implements VotifierScheduler {
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));
@@ -1,9 +1,9 @@
package com.vexsoftware.votifier.bungee;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
import com.google.common.collect.ImmutableList;
import com.vexsoftware.votifier.VoteHandler;
import com.vexsoftware.votifier.bungee.cmd.NVReloadCmd;
import com.vexsoftware.votifier.bungee.cmd.TestVoteCmd;
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
import com.vexsoftware.votifier.platform.BackendServer;
import com.vexsoftware.votifier.platform.JavaUtilLogger;
@@ -21,26 +21,26 @@ import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
import com.vexsoftware.votifier.util.IOUtil;
import com.vexsoftware.votifier.util.KeyCreator;
import com.vexsoftware.votifier.util.TokenUtil;
import io.netty.channel.Channel;
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.api.plugin.PluginManager;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.Key;
import java.security.KeyPair;
import java.util.*;
@@ -81,10 +81,10 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
private LoggingAdapter pluginLogger;
private void loadAndBind() {
scheduler = new BungeeScheduler(this);
pluginLogger = new JavaUtilLogger(getLogger());
if (!getDataFolder().exists()) {
getDataFolder().mkdir();
if (!getDataFolder().mkdir()) {
throw new RuntimeException("Unable to create the plugin data folder " + getDataFolder());
}
}
// Handle configuration.
@@ -98,12 +98,14 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
getLogger().info("Configuring Votifier for the first time...");
// Initialize the configuration file.
config.createNewFile();
if (!config.createNewFile()) {
throw new IOException("Unable to create the config file at " + config);
}
String cfgStr = new String(ByteStreams.toByteArray(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
String cfgStr = new String(IOUtil.readAllBytes(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
String token = TokenUtil.newToken();
cfgStr = cfgStr.replace("%default_token%", token);
Files.asCharSink(config, StandardCharsets.UTF_8).write(cfgStr);
Files.copy(new ByteArrayInputStream(cfgStr.getBytes(StandardCharsets.UTF_8)), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
/*
* Remind hosted server admins to be sure they have the right
@@ -141,7 +143,9 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
*/
try {
if (!rsaDirectory.exists()) {
rsaDirectory.mkdir();
if (!rsaDirectory.mkdir()) {
throw new RuntimeException("Unable to create the RSA key folder " + rsaDirectory);
}
keyPair = RSAKeygen.generate(2048);
RSAIO.save(rsaDirectory, keyPair);
} else {
@@ -161,7 +165,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
}
} else {
String token = TokenUtil.newToken();
configuration.set("tokens", ImmutableMap.of("default", token));
configuration.set("tokens", Collections.singletonMap("default", token));
tokens.put("default", KeyCreator.createKeyFrom(token));
try {
ConfigurationProvider.getProvider(YamlConfiguration.class).save(configuration, config);
@@ -220,13 +224,13 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
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(), this,
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,
this,
new File(getDataFolder(), fwdCfg.getString("pluginMessaging.file.name")),
fwdCfg.getInt("pluginMessaging.file.cacheTime", -1));
} catch (IOException e) {
@@ -253,6 +257,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
String fallbackServer = fwdCfg.getString("pluginMessaging.joinedServerFallback", null);
if (fallbackServer != null && fallbackServer.isEmpty()) fallbackServer = null;
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, filter, voteCache, fallbackServer, dumpRate);
getLogger().info("Forwarding votes over PluginMessaging channel '" + channel + "' for vote forwarding for online players!");
} catch (RuntimeException e) {
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
}
@@ -287,7 +292,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
}
}
forwardingMethod = new ProxyForwardingVoteSource(this, bootstrap::client, serverList, null);
forwardingMethod = bootstrap.createForwardingSource(serverList, null);
getLogger().info("Forwarding votes from this NuVotifier instance to another NuVotifier server.");
} else {
getLogger().severe("No vote forwarding method '" + fwdMethod + "' known. Defaulting to noop implementation.");
@@ -296,17 +301,15 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
@Override
public void onEnable() {
loadAndBind();
scheduler = new BungeeScheduler(this);
pluginLogger = new JavaUtilLogger(getLogger());
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();
}
});
PluginManager pm = ProxyServer.getInstance().getPluginManager();
pm.registerCommand(this, new NVReloadCmd(this));
pm.registerCommand(this, new TestVoteCmd(this));
pm.registerListener(this, new ReloadListener(this));
loadAndBind();
}
private void halt() {
@@ -322,7 +325,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
}
}
public void reload() {
public boolean reload() {
try {
halt();
} catch (Exception ex) {
@@ -332,6 +335,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
try {
loadAndBind();
getLogger().info("Reload was successful.");
return true;
} catch (Exception ex) {
try {
halt();
@@ -340,6 +344,7 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
getLogger().log(Level.SEVERE, "On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!", ex);
getLogger().log(Level.SEVERE, "(halt exception)", ex2);
}
return false;
}
}
@@ -350,33 +355,34 @@ public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlug
}
@Override
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) {
if (debug) {
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
getLogger().info("Got a protocol v1 vote record from " + remoteAddress + " -> " + vote);
} else {
getLogger().info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
getLogger().info("Got a protocol v2 vote record from " + remoteAddress + " -> " + vote);
}
}
getProxy().getScheduler().runAsync(this, () -> getProxy().getPluginManager().callEvent(new VotifierEvent(vote)));
if (forwardingMethod != null) {
getProxy().getScheduler().runAsync(this, () -> forwardingMethod.forward(vote));
}
getProxy().getScheduler().runAsync(this, () -> {
VotifierEvent event = getProxy().getPluginManager().callEvent(new VotifierEvent(vote));
if (!event.isCancelled() && forwardingMethod != null) {
forwardingMethod.forward(vote);
}
});
}
@Override
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
if (debug) {
if (alreadyHandledVote) {
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
"occurred with a vote from " + channel.remoteAddress(), throwable);
getLogger().log(Level.WARNING, "Vote processed, however an exception " +
"occurred with a vote from " + remoteAddress, throwable);
} else {
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
getLogger().log(Level.WARNING, "Unable to process vote from " + remoteAddress, throwable);
}
} else if (!alreadyHandledVote) {
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
getLogger().log(Level.WARNING, "Unable to process vote from " + remoteAddress);
}
}
@@ -33,6 +33,9 @@ public final class OnlineForwardPluginMessagingForwardingSource extends Abstract
if (p != null && p.getServer() != null &&
serverFilter.isAllowed(p.getServer().getInfo().getName())) {
if (forwardSpecific(new BungeeBackendServer(p.getServer().getInfo()), v)) {
if (plugin.isDebug()) {
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to server " + p.getServer().getInfo().getName());
}
return;
}
}
@@ -0,0 +1,18 @@
package com.vexsoftware.votifier.bungee;
import net.md_5.bungee.api.event.ProxyReloadEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class ReloadListener implements Listener {
private final NuVotifier nuVotifier;
public ReloadListener(NuVotifier nuVotifier) {
this.nuVotifier = nuVotifier;
}
@EventHandler
public void onProxyReload(ProxyReloadEvent event) {
this.nuVotifier.reload();
}
}
@@ -0,0 +1,44 @@
package com.vexsoftware.votifier.bungee.cmd;
import com.vexsoftware.votifier.bungee.NuVotifier;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Command;
public class NVReloadCmd extends Command {
private final NuVotifier plugin;
private static final BaseComponent reloadingNuVotifier = new TextComponent("Reloading NuVotifier...");
private static final BaseComponent nuvotifierHasBeenReloaded = new TextComponent("NuVotifier has been reloaded!");
private static final BaseComponent problem = new TextComponent("Looks like there was a problem reloading NuVotifier, check the console!");
private static final BaseComponent permission = new TextComponent("You do not have permission to do this!");
static {
reloadingNuVotifier.setColor(ChatColor.GRAY);
nuvotifierHasBeenReloaded.setColor(ChatColor.DARK_GREEN);
problem.setColor(ChatColor.DARK_RED);
permission.setColor(ChatColor.DARK_RED);
}
public NVReloadCmd(NuVotifier plugin) {
super("pnvreload", "nuvotifier.reload");
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (sender.hasPermission("nuvotifier.reload")) {
sender.sendMessage(reloadingNuVotifier);
if (plugin.reload()) {
sender.sendMessage(nuvotifierHasBeenReloaded);
} else {
sender.sendMessage(problem);
}
} else {
sender.sendMessage(permission);
}
}
}
@@ -0,0 +1,51 @@
package com.vexsoftware.votifier.bungee.cmd;
import com.vexsoftware.votifier.bungee.NuVotifier;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.ArgsToVote;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.plugin.Command;
public class TestVoteCmd extends Command {
private final NuVotifier plugin;
private static final BaseComponent permission = new TextComponent("You do not have permission to do this!");
private static final BaseComponent usage = new TextComponent("Usage hint: /ptestvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]");
static {
usage.setColor(ChatColor.GRAY);
}
public TestVoteCmd(NuVotifier plugin) {
super("ptestvote", "nuvotifier.testvote");
this.plugin = plugin;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (sender.hasPermission("nuvotifier.testvote")) {
Vote v;
try {
v = ArgsToVote.parse(args);
} catch (IllegalArgumentException e) {
TextComponent c = new TextComponent("Error while parsing arguments to create test vote: " + e.getMessage());
c.setColor(ChatColor.DARK_RED);
sender.sendMessage(c);
sender.sendMessage(usage);
return;
}
plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
TextComponent c = new TextComponent("Test vote executed: " + v.toString());
c.setColor(ChatColor.GREEN);
sender.sendMessage(c);
} else {
sender.sendMessage(permission);
}
}
}
@@ -1,18 +1,21 @@
package com.vexsoftware.votifier.bungee.events;
import com.vexsoftware.votifier.model.Vote;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
/**
* {@code VotifierEvent} is a custom BungeeCord event class that is sent
* asynchronously allowing other plugins to listen for votes.
*/
public class VotifierEvent extends Event {
public class VotifierEvent extends Event implements Cancellable {
/**
* The vote.
**/
private final Vote vote;
private boolean cancelled;
public VotifierEvent(Vote vote) {
this.vote = vote;
}
@@ -20,4 +23,14 @@ public class VotifierEvent extends Event {
public Vote getVote() {
return vote;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
name: NuVotifier
main: com.vexsoftware.votifier.bungee.NuVotifier
version: @app.version@
version: "${internalVersion}"
author: Ichbinjoe, blakeman8192, Kramer, tuxed
-7
View File
@@ -1,7 +0,0 @@
dependencies {
compile group: "io.netty", name: "netty-handler", version: "4.1.32.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
}
+15
View File
@@ -0,0 +1,15 @@
plugins {
`java-library`
}
applyPlatformAndCoreConfiguration()
applyCommonArtifactoryConfig()
dependencies {
"api"(project(":nuvotifier-api"))
"implementation"("io.netty:netty-handler:${Versions.NETTYIO}")
"implementation"("io.netty:netty-transport-native-epoll:${Versions.NETTYIO}:linux-x86_64")
"implementation"("com.google.code.gson:gson:${Versions.GSON}")
"testImplementation"("org.json:json:20180130") // retain this for testing reasons
"testImplementation"("com.google.guava:guava:28.1-jre")
}
@@ -5,16 +5,11 @@ import com.vexsoftware.votifier.net.VotifierSession;
import io.netty.channel.Channel;
public interface VoteHandler {
void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception;
// 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);
}
void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception;
@Deprecated
default void onError(Channel channel, Throwable throwable) {
default void onError(Throwable throwable, boolean voteAlreadyCompleted, String remoteAddress) {
throw new RuntimeException("Unimplemented onError handler");
}
}
@@ -1,29 +1,43 @@
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 com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.FastThreadLocalThread;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class VotifierServerBootstrap {
private static final boolean USE_EPOLL = Epoll.isAvailable();
private final String host;
private final int port;
private final NioEventLoopGroup bossLoopGroup;
private final NioEventLoopGroup eventLoopGroup;
private final EventLoopGroup bossLoopGroup;
private final EventLoopGroup eventLoopGroup;
private final VotifierPlugin plugin;
private final boolean v1Disable;
@@ -34,23 +48,41 @@ public class VotifierServerBootstrap {
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());
if (USE_EPOLL) {
this.bossLoopGroup = new EpollEventLoopGroup(1, createThreadFactory("Votifier epoll boss"));
this.eventLoopGroup = new EpollEventLoopGroup(3, createThreadFactory("Votifier epoll worker"));
plugin.getPluginLogger().info("Using epoll transport to accept votes.");
} else {
this.bossLoopGroup = new NioEventLoopGroup(1, createThreadFactory("Votifier NIO boss"));
this.eventLoopGroup = new NioEventLoopGroup(3, createThreadFactory("Votifier NIO worker"));
plugin.getPluginLogger().info("Using NIO transport to accept votes.");
}
}
private static ThreadFactory createThreadFactory(String name) {
return runnable -> {
FastThreadLocalThread thread = new FastThreadLocalThread(runnable, name);
thread.setDaemon(true);
return thread;
};
}
public void start(Consumer<Throwable> error) {
Objects.requireNonNull(error, "error");
VoteInboundHandler voteInboundHandler = new VoteInboundHandler(plugin);
new ServerBootstrap()
.channel(NioServerSocketChannel.class)
.channel(USE_EPOLL ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
.group(bossLoopGroup, eventLoopGroup)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) {
protected void initChannel(SocketChannel channel) {
channel.attr(VotifierSession.KEY).set(new VotifierSession());
channel.attr(VotifierPlugin.KEY).set(plugin);
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
channel.pipeline().addLast("greetingHandler", VotifierGreetingHandler.INSTANCE);
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !v1Disable));
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(plugin));
channel.pipeline().addLast("voteHandler", voteInboundHandler);
}
})
.bind(host, port)
@@ -70,12 +102,17 @@ public class VotifierServerBootstrap {
});
}
public Bootstrap client() {
private Bootstrap client() {
return new Bootstrap()
.channel(NioSocketChannel.class)
.channel(USE_EPOLL ? EpollSocketChannel.class : NioSocketChannel.class)
.group(eventLoopGroup);
}
public ProxyForwardingVoteSource createForwardingSource(List<ProxyForwardingVoteSource.BackendServer> backendServers,
VoteCache voteCache) {
return new ProxyForwardingVoteSource(plugin, this::client, backendServers, voteCache);
}
public void shutdown() {
if (serverChannel != null) {
try {
@@ -86,5 +123,12 @@ public class VotifierServerBootstrap {
}
eventLoopGroup.shutdownGracefully();
bossLoopGroup.shutdownGracefully();
try {
bossLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
eventLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@@ -40,8 +40,14 @@ public class VotifierSession {
}
public enum ProtocolVersion {
UNKNOWN,
ONE,
TWO
UNKNOWN("unknown"),
ONE("protocol v1"),
TWO("protocol v2"),
TEST("test");
public final String humanReadable;
ProtocolVersion(String hr) {
this.humanReadable = hr;
}
}
}
@@ -6,21 +6,29 @@ 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.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.concurrent.atomic.AtomicLong;
@ChannelHandler.Sharable
public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
private final VoteHandler handler;
private final AtomicLong lastError;
private final AtomicLong errorsSent;
public VoteInboundHandler(VoteHandler handler) {
this.handler = handler;
this.lastError = new AtomicLong();
this.errorsSent = new AtomicLong();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, final Vote vote) throws Exception {
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
handler.onVoteReceived(ctx.channel(), vote, session.getVersion());
handler.onVoteReceived(vote, session.getVersion(), ctx.channel().remoteAddress().toString());
session.completeVote();
if (session.getVersion() == VotifierSession.ProtocolVersion.ONE) {
@@ -36,7 +44,8 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
handler.onError(ctx.channel(), session.hasCompletedVote(), cause);
String remoteAddr = ctx.channel().remoteAddress().toString();
boolean hasCompletedVote = session.hasCompletedVote();
if (session.getVersion() == VotifierSession.ProtocolVersion.TWO) {
JsonObject object = new JsonObject();
@@ -47,5 +56,22 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
} else {
ctx.close();
}
if (!willThrottleErrorLogging()) {
handler.onError(cause, hasCompletedVote, remoteAddr);
}
}
private boolean willThrottleErrorLogging() {
long lastErrorAt = this.lastError.get();
long now = System.currentTimeMillis();
if (lastErrorAt + 2000 >= now) {
return this.errorsSent.incrementAndGet() >= 5;
} else {
this.lastError.set(now);
this.errorsSent.set(0);
return false;
}
}
}
@@ -3,6 +3,7 @@ package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.net.VotifierSession;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@@ -11,7 +12,12 @@ import java.nio.charset.StandardCharsets;
/**
* Handles the Votifier greeting.
*/
@ChannelHandler.Sharable
public class VotifierGreetingHandler extends ChannelInboundHandlerAdapter {
public static final VotifierGreetingHandler INSTANCE = new VotifierGreetingHandler();
private VotifierGreetingHandler() {}
@Override
public void channelActive(ChannelHandlerContext ctx) {
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
@@ -3,7 +3,9 @@ package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
import com.vexsoftware.votifier.util.QuietException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
@@ -16,33 +18,46 @@ import java.util.List;
*/
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
if (buf.readableBytes() < 256) {
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
if (!ctx.channel().isActive()) {
buf.skipBytes(buf.readableBytes());
return;
}
byte[] block = new byte[buf.readableBytes()];
buf.getBytes(0, block);
// "Drain" the whole buffer
buf.readerIndex(buf.readableBytes());
if (buf.readableBytes() < 256) {
// The client might have not sent all the data yet, so don't eject the connection.
return;
}
if (buf.readableBytes() > 256) {
// They sent too much data.
throw new QuietException("Could not decrypt data from " + ctx.channel().remoteAddress() + " as it is too long. Attack?");
}
byte[] block = ByteBufUtil.getBytes(buf);
buf.skipBytes(buf.readableBytes());
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
try {
block = RSA.decrypt(block, plugin.getProtocolV1Key().getPrivate());
} catch (Exception e) {
throw new CorruptedFrameException("Could not decrypt data (is your key correct?)", e);
if (plugin.isDebug()) {
throw new CorruptedFrameException("Could not decrypt data from " + ctx.channel().remoteAddress() + ". Make sure the public key on the list is correct.", e);
} else {
throw new QuietException("Could not decrypt data from " + ctx.channel().remoteAddress() + ". Make sure the public key on the list is correct.");
}
}
// Parse the string we received.
String all = new String(block, StandardCharsets.US_ASCII);
String[] split = all.split("\n");
if (split.length < 5) {
throw new CorruptedFrameException("Not enough fields specified in vote.");
throw new QuietException("Not enough fields specified in vote. This is not a NuVotifier issue. Got " + split.length + " fields, but needed 5.");
}
if (!split[0].equals("VOTE")) {
throw new CorruptedFrameException("VOTE opcode not found");
throw new QuietException("The VOTE opcode was not present. This is not a NuVotifier issue, but a bug with the server list.");
}
// Create the vote.
@@ -1,10 +1,10 @@
package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.QuietException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
@@ -16,6 +16,8 @@ import java.util.List;
* Attempts to determine if original protocol or protocol v2 is being used.
*/
public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
private static final QuietException V2_ONLY = new QuietException("This server only accepts well-formed Votifier v2 packets.");
private static final short PROTOCOL_2_MAGIC = 0x733A;
private final boolean testMode;
private final boolean allowv1;
@@ -26,7 +28,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
// Determine the number of bytes that are available.
int readable = buf.readableBytes();
@@ -35,11 +37,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
return;
}
short readMagic = buf.readShort();
// Reset reader index again
buf.readerIndex(0);
short readMagic = buf.getShort(0);
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
if (readMagic == PROTOCOL_2_MAGIC) {
@@ -55,7 +53,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
}
} else {
if (!allowv1) {
throw new CorruptedFrameException("This server only accepts well-formed Votifier v2 packets.");
throw V2_ONLY;
}
// Probably Protocol v1 Vote Message
session.setVersion(VotifierSession.ProtocolVersion.ONE);
@@ -63,7 +63,7 @@ public class RSAIO {
public static byte[] readB64File(File directory, String name) throws IOException {
File f = new File(directory, name);
byte[] contents = Files.readAllBytes(f.toPath());
String strContents = new String(contents);
String strContents = new String(contents, StandardCharsets.US_ASCII);
strContents = strContents.trim();
try {
return Base64.getDecoder().decode(strContents);
@@ -1,8 +1,7 @@
package com.vexsoftware.votifier.platform;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class JavaUtilLogger implements LoggingAdapter {
@@ -23,6 +22,14 @@ public class JavaUtilLogger implements LoggingAdapter {
logger.log(Level.SEVERE, s, o);
}
@Override
public void error(String s, Throwable e, Object... o) {
LogRecord lr = new LogRecord(Level.SEVERE, s);
lr.setParameters(o);
lr.setThrown(e);
logger.log(lr);
}
@Override
public void warn(String s) {
logger.warning(s);
@@ -3,6 +3,7 @@ package com.vexsoftware.votifier.platform;
public interface LoggingAdapter {
void error(String s);
void error(String s, Object... o);
void error(String s, Throwable e, Object... o);
void warn(String s);
void warn(String s, Object... o);
@@ -1,7 +1,6 @@
package com.vexsoftware.votifier.platform.scheduler;
import com.google.common.base.Preconditions;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -10,22 +9,7 @@ public class ScheduledExecutorServiceVotifierScheduler implements VotifierSchedu
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));
this.service = Objects.requireNonNull(service, "service");
}
@Override
@@ -3,11 +3,6 @@ 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);
@@ -39,7 +39,11 @@ public abstract class AbstractPluginMessagingForwardingSource implements Forward
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());
if (!forwardSpecific(server, rawData)) {
attemptToAddToCache(v, server.getName());
} else if (plugin.isDebug()) {
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to server " + server.getName());
}
}
}
@@ -137,7 +141,7 @@ public abstract class AbstractPluginMessagingForwardingSource implements Forward
// cachedVotes contains any votes which have yet to be evicted
cb.accept(cachedVotes);
}, 1, TimeUnit.SECONDS);
}, evictedAlready == 0 ? 3 : 1, TimeUnit.SECONDS);
} else {
cb.accept(cachedVotes);
}
@@ -11,7 +11,7 @@ public class ServerFilter {
}
public ServerFilter() {
this(Collections.EMPTY_SET, true);
this(Collections.emptySet(), true);
}
private final Collection<String> names;
@@ -1,10 +1,8 @@
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;
@@ -12,6 +10,8 @@ import com.vexsoftware.votifier.util.GsonInst;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.util.*;
import java.util.concurrent.TimeUnit;
@@ -21,8 +21,8 @@ public class FileVoteCache extends MemoryVoteCache {
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);
public FileVoteCache(final VotifierPlugin plugin, File cacheFile, long voteTTL) throws IOException {
super(plugin, voteTTL);
this.cacheFile = cacheFile;
this.l = plugin.getPluginLogger();
@@ -48,9 +48,13 @@ public class FileVoteCache extends MemoryVoteCache {
private void load() throws IOException {
// Load the cache from disk
JsonObject object;
try (BufferedReader reader = Files.newReader(cacheFile, StandardCharsets.UTF_8)) {
try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath(), StandardCharsets.UTF_8)) {
object = GsonInst.gson.fromJson(reader, JsonObject.class);
} catch (FileNotFoundException e) {
if (object == null) {
// When the input is not malformed but instead empty, this returns null. Simply assume it is empty.
object = new JsonObject();
}
} catch (NoSuchFileException e) {
object = new JsonObject();
}
@@ -73,11 +77,11 @@ public class FileVoteCache extends MemoryVoteCache {
JsonObject servers = object.getAsJsonObject("servers");
for (String player : keySet(players)) {
playerVoteCache.putAll(player, readVotes(players.getAsJsonArray(player)));
playerVoteCache.put(player, readVotes(players.getAsJsonArray(player)));
}
for (String server : keySet(servers)) {
voteCache.putAll(server, readVotes(servers.getAsJsonArray(server)));
voteCache.put(server, readVotes(servers.getAsJsonArray(server)));
}
if (resave) {
@@ -98,11 +102,11 @@ public class FileVoteCache extends MemoryVoteCache {
}
}
private Collection<Vote> readVotes(JsonArray voteArray) {
List<Vote> votes = new ArrayList<>(voteArray.size());
private Collection<VoteWithRecordedTimestamp> readVotes(JsonArray voteArray) {
Collection<VoteWithRecordedTimestamp> votes = new HashSet<>(voteArray.size());
for (int i = 0; i < voteArray.size(); i++) {
JsonObject voteObject = voteArray.get(i).getAsJsonObject();
Vote v = new Vote(voteObject);
VoteWithRecordedTimestamp v = new VoteWithRecordedTimestamp(voteObject);
if (hasTimedOut(v))
l.warn("Purging out of date vote.", v);
else
@@ -112,31 +116,32 @@ public class FileVoteCache extends MemoryVoteCache {
}
public void save() throws IOException {
cacheLock.lock();
JsonObject votesObject = new JsonObject();
votesObject.addProperty("version", 2);
cacheLock.lock();
try {
votesObject.add("players", serializeMap(playerVoteCache.asMap()));
votesObject.add("servers", serializeMap(voteCache.asMap()));
votesObject.addProperty("version", 2);
votesObject.add("players", serializeMap(playerVoteCache));
votesObject.add("servers", serializeMap(voteCache));
} finally {
cacheLock.unlock();
}
try (BufferedWriter writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) {
try (BufferedWriter writer = Files.newBufferedWriter(cacheFile.toPath(), StandardCharsets.UTF_8)) {
GsonInst.gson.toJson(votesObject, writer);
}
}
public JsonObject serializeMap(Map<String, Collection<Vote>> map) {
public JsonObject serializeMap(Map<String, Collection<VoteWithRecordedTimestamp>> map) {
JsonObject o = new JsonObject();
Iterator<Map.Entry<String, Collection<Vote>>> entryItr = map.entrySet().iterator();
Iterator<Map.Entry<String, Collection<VoteWithRecordedTimestamp>>> entryItr = map.entrySet().iterator();
while (entryItr.hasNext()) {
Map.Entry<String, Collection<Vote>> entry = entryItr.next();
Map.Entry<String, Collection<VoteWithRecordedTimestamp>> entry = entryItr.next();
JsonArray array = new JsonArray();
Iterator<Vote> voteItr = entry.getValue().iterator();
Iterator<VoteWithRecordedTimestamp> voteItr = entry.getValue().iterator();
while (voteItr.hasNext()) {
Vote vote = voteItr.next();
VoteWithRecordedTimestamp vote = voteItr.next();
// if the vote is no longer valid, notify and remove
if (hasTimedOut(vote)) {
@@ -1,8 +1,6 @@
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.google.gson.JsonObject;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
@@ -20,16 +18,16 @@ 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 Map<String, Collection<VoteWithRecordedTimestamp>> voteCache;
protected final Map<String, Collection<VoteWithRecordedTimestamp>> 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();
public MemoryVoteCache(VotifierPlugin p, long voteTTL) {
voteCache = new HashMap<>();
playerVoteCache = new HashMap<>();
this.voteTTL = voteTTL;
@@ -41,7 +39,7 @@ public class MemoryVoteCache implements VoteCache {
public Collection<String> getCachedServers() {
cacheLock.lock();
try {
return ImmutableList.copyOf(voteCache.keySet());
return Collections.unmodifiableCollection(new ArrayList<>(voteCache.keySet()));
} finally {
cacheLock.unlock();
}
@@ -52,7 +50,7 @@ public class MemoryVoteCache implements VoteCache {
if (server == null) throw new NullPointerException();
cacheLock.lock();
try {
voteCache.put(server, v);
voteCache.computeIfAbsent(server, k -> new HashSet<>()).add(new VoteWithRecordedTimestamp(v));
} finally {
cacheLock.unlock();
}
@@ -63,7 +61,7 @@ public class MemoryVoteCache implements VoteCache {
if (player == null) throw new NullPointerException();
cacheLock.lock();
try {
playerVoteCache.put(player, v);
playerVoteCache.computeIfAbsent(player, k -> new HashSet<>()).add(new VoteWithRecordedTimestamp(v));
} finally {
cacheLock.unlock();
}
@@ -74,7 +72,12 @@ public class MemoryVoteCache implements VoteCache {
if (player == null) throw new NullPointerException();
cacheLock.lock();
try {
return new HashSet<>(playerVoteCache.removeAll(player));
Collection<VoteWithRecordedTimestamp> playerVotes = playerVoteCache.remove(player);
if (playerVotes != null) {
return new HashSet<>(playerVotes);
} else {
return Collections.emptySet();
}
} finally {
cacheLock.unlock();
}
@@ -85,7 +88,12 @@ public class MemoryVoteCache implements VoteCache {
if (server == null) throw new NullPointerException();
cacheLock.lock();
try {
return new HashSet<>(voteCache.removeAll(server));
Collection<VoteWithRecordedTimestamp> serverVotes = voteCache.remove(server);
if (serverVotes != null) {
return new HashSet<>(serverVotes);
} else {
return Collections.emptySet();
}
} finally {
cacheLock.unlock();
}
@@ -101,20 +109,67 @@ public class MemoryVoteCache implements VoteCache {
}
}
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();
private void sweep(Map<String, Collection<VoteWithRecordedTimestamp>> m) {
for (Map.Entry<String, Collection<VoteWithRecordedTimestamp>> entry : m.entrySet()) {
Iterator<VoteWithRecordedTimestamp> vi = entry.getValue().iterator();
while (vi.hasNext()) {
VoteWithRecordedTimestamp v = vi.next();
if (hasTimedOut(v)) {
l.warn("Purging out of date vote.", v);
vi.remove();
}
}
}
}
protected boolean hasTimedOut(Vote v) {
protected boolean hasTimedOut(VoteWithRecordedTimestamp v) {
if (voteTTL == -1) return false;
// scale voteTTL to milliseconds
return v.getLocalTimestamp() + voteTTL * 24 * 60 * 60 * 1000 < System.currentTimeMillis();
long daysAsMillis = TimeUnit.DAYS.toMillis(voteTTL);
return v.recorded + daysAsMillis < System.currentTimeMillis();
}
static class VoteWithRecordedTimestamp extends Vote {
private final long recorded;
VoteWithRecordedTimestamp(Vote vote) {
super(vote);
this.recorded = System.currentTimeMillis();
}
VoteWithRecordedTimestamp(JsonObject object) {
super(object);
if (object.has("recorded")) {
this.recorded = object.get("recorded").getAsLong();
} else {
// Assign the current time, in the hope that the stale votes will eventually get purged.
this.recorded = System.currentTimeMillis();
}
}
@Override
public JsonObject serialize() {
JsonObject object = super.serialize();
object.addProperty("recorded", this.recorded);
return object;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
VoteWithRecordedTimestamp that = (VoteWithRecordedTimestamp) o;
return recorded == that.recorded;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (int) (recorded ^ (recorded >>> 32));
return result;
}
}
}
@@ -51,6 +51,6 @@ public interface VoteCache {
* @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;
return Collections.emptyList();
}
}
@@ -10,7 +10,7 @@ import com.vexsoftware.votifier.model.Vote;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
@@ -53,9 +53,9 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
nettyBootstrap.get()
.handler(new ChannelInitializer<NioSocketChannel>() {
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) {
protected void initChannel(SocketChannel channel) {
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
channel.pipeline().addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
channel.pipeline().addLast(STRING_DECODER);
@@ -0,0 +1,62 @@
package com.vexsoftware.votifier.util;
import com.vexsoftware.votifier.model.Vote;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ArgsToVote {
private static final Pattern argumentPattern = Pattern.compile("([a-zA-Z]+)=(\\S*)");
public static Vote parse(String[] arguments) {
return parse(arguments, null);
}
public static Vote parse(String[] arguments, Map<String, String> additionalArgs) {
String serviceName = "TestVote";
String username = null;
String address = "127.0.0.1";
long localTimestamp = System.currentTimeMillis();
String timestamp = Long.toString(localTimestamp, 10);
for (String s : arguments) {
Matcher m = argumentPattern.matcher(s);
if (m.matches()) {
String key = m.group(1).toLowerCase();
String v = m.group(2);
switch (key) {
case "servicename":
serviceName = v;
break;
case "username":
if (v.length() > 16)
throw new IllegalArgumentException("Illegal username - must be less than 16 characters long.");
username = v;
break;
case "address":
address = v;
break;
case "timestamp":
timestamp = v;
break;
default:
if (additionalArgs != null)
additionalArgs.put(key, v);
break;
}
} else {
if (s.length() > 16)
throw new IllegalArgumentException("Illegal username - must be less than 16 characters long.");
username = s; // ezpz
}
}
if (username == null)
throw new IllegalArgumentException("Username not specified!");
return new Vote(serviceName, username, address, timestamp);
}
}
@@ -0,0 +1,17 @@
package com.vexsoftware.votifier.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class IOUtil {
public static byte[] readAllBytes(InputStream from) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(8192);
byte[] buf = new byte[8192];
int read;
while ((read = from.read(buf)) != -1) {
out.write(buf, 0, read);
}
return out.toByteArray();
}
}
@@ -0,0 +1,15 @@
package com.vexsoftware.votifier.util;
public class QuietException extends Exception {
public QuietException() {
}
public QuietException(String s) {
super(s);
}
@Override
public synchronized Throwable fillInStackTrace() {
return null;
}
}
@@ -1,14 +1,13 @@
package com.vexsoftware.votifier.util.standalone;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.platform.JavaUtilLogger;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.platform.scheduler.ScheduledExecutorServiceVotifierScheduler;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import io.netty.channel.Channel;
import java.net.InetSocketAddress;
import java.security.Key;
@@ -69,12 +68,12 @@ public class StandaloneVotifierPlugin implements VotifierPlugin {
}
@Override
public void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception {
receiver.onVote(vote);
}
@Override
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
receiver.onException(throwable);
}
}
@@ -85,6 +85,11 @@ public class TestVotifierPlugin implements VotifierPlugin {
return null;
}
@Override
public boolean isDebug() {
return true;
}
public void specificKeysOnly() {
keyMap.clear();
keyMap.put("Test", KeyCreator.createKeyFrom("test"));
@@ -96,7 +101,7 @@ public class TestVotifierPlugin implements VotifierPlugin {
}
@Override
public void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception {
}
}
@@ -26,6 +26,6 @@ public class VoteUtil {
builder.append(s).append('\n'); // naive join needed!
}
return RSA.encrypt(builder.toString().getBytes(StandardCharsets.UTF_8), key);
return RSA.encrypt(builder.toString().getBytes(StandardCharsets.US_ASCII), key);
}
}
@@ -4,18 +4,20 @@ import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
import com.vexsoftware.votifier.util.QuietException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.CorruptedFrameException;
import org.junit.Test;
import io.netty.handler.codec.DecoderException;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import static org.junit.Assert.*;
import static org.junit.jupiter.api.Assertions.*;
public class VotifierProtocol1DecoderTest {
private static final VotifierSession SESSION = new VotifierSession();
@@ -50,24 +52,21 @@ public class VotifierProtocol1DecoderTest {
byte[] encrypted = RSA.encrypt(bad.getBytes(StandardCharsets.UTF_8), TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
try {
channel.writeInbound(encryptedByteBuf);
} finally {
channel.close();
}
assertThrows(DecoderException.class, () -> channel.writeInbound(encryptedByteBuf));
channel.close();
}
@Test(expected = CorruptedFrameException.class)
@Test
public void testFailureDecodeMissingField() throws Exception {
verifyFailure("VOTE\nTest\ntest\ntest"); // missing field
}
@Test(expected = CorruptedFrameException.class)
@Test
public void testFailureDecodeBadOpcode() throws Exception {
verifyFailure("TEST\nTest\ntest\ntest\ntest\n");
}
@Test(expected = CorruptedFrameException.class)
@Test
public void testFailureDecodeBadRsa() throws Exception {
// Decode our bad RSA key
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
@@ -80,10 +79,7 @@ public class VotifierProtocol1DecoderTest {
byte[] encrypted = VoteUtil.encodePOJOv1(new Vote("Test", "test", "test", "test"), badPublicKey);
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
try {
channel.writeInbound(encryptedByteBuf);
} finally {
channel.close();
}
assertThrows(DecoderException.class, ()->channel.writeInbound(encryptedByteBuf));
channel.close();
}
}
@@ -10,14 +10,14 @@ import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
import org.json.JSONObject;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import javax.crypto.Mac;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;
import static org.junit.Assert.*;
import static org.junit.jupiter.api.Assertions.*;
public class VotifierProtocol2DecoderTest {
private static final VotifierSession SESSION = new VotifierSession();
@@ -61,7 +61,7 @@ public class VotifierProtocol2DecoderTest {
sendVote(new Vote("Test", "test", "test", "0"), TestVotifierPlugin.getI().getTokens().get("default"), true);
}
@Test(expected = DecoderException.class)
@Test
public void testFailureDecodeBadPacket() {
// Create a well-formed request
EmbeddedChannel channel = createChannel();
@@ -73,14 +73,11 @@ public class VotifierProtocol2DecoderTest {
object.put("payload", GsonInst.gson.toJson(payload));
// We "forget" the signature.
try {
channel.writeInbound(object.toString());
} finally {
channel.close();
}
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
channel.close();
}
@Test(expected = DecoderException.class)
@Test
public void testFailureDecodeBadVoteField() throws Exception {
// Create a well-formed request
EmbeddedChannel channel = createChannel();
@@ -96,14 +93,11 @@ public class VotifierProtocol2DecoderTest {
object.put("signature",
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
try {
channel.writeInbound(object.toString());
} finally {
channel.close();
}
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
channel.close();
}
@Test(expected = DecoderException.class)
@Test
public void testFailureDecodeBadChallenge() throws Exception {
// Create a well-formed request
EmbeddedChannel channel = createChannel();
@@ -120,29 +114,24 @@ public class VotifierProtocol2DecoderTest {
object.put("signature",
Base64.getEncoder().encode(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
try {
channel.writeInbound(object.toString());
} finally {
channel.close();
}
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
channel.close();
}
@Test(expected = DecoderException.class)
@Test
public void testFailureDecodeNonExistentKey() throws Exception {
TestVotifierPlugin.getI().specificKeysOnly();
Vote vote = new Vote("Bad Service", "test", "test", "0");
try {
sendVote(vote, TestVotifierPlugin.getI().getTokens().get("Test"), false);
} finally {
TestVotifierPlugin.getI().restoreDefault();
}
assertThrows(DecoderException.class, () -> sendVote(vote, TestVotifierPlugin.getI().getTokens().get("Test"), false));
TestVotifierPlugin.getI().restoreDefault();
}
@Test(expected = CorruptedFrameException.class)
public void testFailureDecodeBadSignature() throws Exception {
@Test
public void testFailureDecodeBadSignature() {
Vote vote = new Vote("Bad Service", "test", "test", "0");
sendVote(vote, KeyCreator.createKeyFrom("BadKey"), false);
assertThrows(CorruptedFrameException.class, () -> sendVote(vote, KeyCreator.createKeyFrom("BadKey"), false));
}
}
@@ -2,13 +2,16 @@ package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.QuietException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class VotifierProtocolDifferentiatorTest {
@Test
@@ -43,7 +46,7 @@ public class VotifierProtocolDifferentiatorTest {
channel.close();
}
@Test(expected = DecoderException.class)
@Test
public void failIfv1NotSupported() {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
@@ -54,13 +57,11 @@ public class VotifierProtocolDifferentiatorTest {
for (int i = 0; i < 256; i++) {
test.writeByte(0);
}
channel.writeInbound(test);
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
assertThrows(DecoderException.class, () -> channel.writeInbound(test));
channel.close();
}
@Test(expected = DecoderException.class)
@Test
public void failOnBadPacketTest() {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
@@ -69,11 +70,8 @@ public class VotifierProtocolDifferentiatorTest {
buf.writeByte(0);
}
try {
channel.writeInbound(buf);
} finally {
channel.close();
}
assertThrows(DecoderException.class, () -> channel.writeInbound(buf));
channel.close();
}
@Test
@@ -1,26 +1,26 @@
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 org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class PMForwardingSinkTest {
@Test
public void testSuccessfulMultiDecode() throws Exception {
List<Vote> receivedVotes = Lists.newArrayList();
List<Vote> receivedVotes = new ArrayList<>();
ForwardedVoteListener vl = receivedVotes::add;
List<Vote> sentVotes = Lists.newArrayList(
List<Vote> sentVotes = new ArrayList<>(Arrays.asList(
new Vote("serviceA", "usernameA", "1.1.1.1", "1546300800"),
new Vote("serviceB", "usernameBBBBBBB", "1.2.23.4", "1514764800", System.currentTimeMillis())
);
new Vote("serviceB", "usernameBBBBBBB", "1.2.23.4", "1514764800")
));
StringBuilder message = new StringBuilder();
@@ -28,7 +28,7 @@ public class PMForwardingSinkTest {
message.append(v.serialize());
}
byte[] messageBytes = message.toString().getBytes();
byte[] messageBytes = message.toString().getBytes(StandardCharsets.UTF_8);
System.out.println(message.toString());
AbstractPluginMessagingForwardingSink sink = new AbstractPluginMessagingForwardingSink(vl) {
@@ -0,0 +1,54 @@
package com.vexsoftware.votifier.util;
import com.vexsoftware.votifier.model.Vote;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class ArgsToVoteTest {
@Test
void handlesInvalidRequest() {
assertThrows(IllegalArgumentException.class, () -> {
ArgsToVote.parse(new String[0]);
});
}
@Test
void handlesSingleUsername() {
Vote vote = ArgsToVote.parse(new String[] { "dummy_user" });
assertEquals("dummy_user", vote.getUsername());
}
@Test
void handlesSingleUsernameTooLong() {
assertThrows(IllegalArgumentException.class, () -> {
ArgsToVote.parse(new String[] { "AVeryVeryVeryLongUsername" });
});
}
@Test
void handleServiceNameGetter() {
Vote vote = ArgsToVote.parse(new String[] { "username=abc123", "servicename=dummy" });
assertEquals("abc123", vote.getUsername());
assertEquals("dummy", vote.getServiceName());
}
@Test
void handleAddressGetter() {
Vote vote = ArgsToVote.parse(new String[] { "username=abc123", "address=dummy" });
assertEquals("abc123", vote.getUsername());
assertEquals("dummy", vote.getAddress());
}
@Test
void handleTimestampGetter() {
Vote vote = ArgsToVote.parse(new String[] { "username=abc123", "timestamp=dummy" });
assertEquals("abc123", vote.getUsername());
assertEquals("dummy", vote.getTimeStamp());
}
@Test
void handlesUsernameKeywordArgument() {
Vote vote = ArgsToVote.parse(new String[] { "username=dummy_user" });
assertEquals("dummy_user", vote.getUsername());
}
}
+16 -6
View File
@@ -1,22 +1,32 @@
.PHONY: clean, all, run
.PHONY: clean all run
all : resources/paper.jar resources/proxy.jar
all : resources/paper.jar resources/waterfall.jar resources/velocity.jar resources/sponge.jar resources/viaversion.jar
resources :
mkdir resources
resources/paper.jar : resources
wget -O resources/paper.jar https://papermc.io/ci/job/Paper-1.13/lastSuccessfulBuild/artifact/paperclip.jar
wget -O resources/paper.jar https://papermc.io/api/v1/paper/1.16.2/182/download
resources/proxy.jar : resources
wget -O resources/proxy.jar https://papermc.io/ci/job/Waterfall/lastSuccessfulBuild/artifact/Waterfall-Proxy/bootstrap/target/Waterfall.jar
resources/waterfall.jar : resources
wget -O resources/waterfall.jar https://papermc.io/ci/job/Waterfall/lastSuccessfulBuild/artifact/Waterfall-Proxy/bootstrap/target/Waterfall.jar
resources/velocity.jar : resources
wget -O resources/velocity.jar https://ci.velocitypowered.com/job/velocity-1.1.0/lastSuccessfulBuild/artifact/proxy/build/libs/velocity-proxy-1.1.0-SNAPSHOT-all.jar
resources/sponge.jar : resources
wget -O resources/sponge.jar https://repo.spongepowered.org/maven/org/spongepowered/spongevanilla/1.12.2-7.3.0/spongevanilla-1.12.2-7.3.0.jar
resources/viaversion.jar : resources
wget -O resources/viaversion.jar https://ci.viaversion.com/job/ViaVersion/459/artifact/jar/target/ViaVersion-3.1.1-SNAPSHOT.jar
clean :
rm -r resources/
run :
tmux new-session './proxy/run.sh' \; \
tmux new-session './p1/run.sh' \; \
split-window -h './p2/run.sh' \; \
split-window -h './s1/run.sh' \; \
split-window -h './s2/run.sh' \; \
select-layout even-horizontal
+10 -7
View File
@@ -1,7 +1,7 @@
## 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.
This testbed runs two proxies, labeled p1 and p2, as well as two servers behind it,
labeled s1 and s2. Ports for where everything is bound is listed below.
First, download required sources using the following command:
@@ -19,8 +19,11 @@ 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 |
| Server | Server Software | MC Port | Votifier Port |
| ------ | ---------------------- | ------- | ------------- |
| p1 | Waterfall/BungeeCord | 25577 | 8192 |
| p2 | Velocity | 25578 | 8193 |
| s1 | Paper (1.16.1) | 25565 | 8194 |
| s2 | SpongeVanilla (1.12.2) | 25566 | 8195 |
For compatibility with 1.16.1, s2 includes ViaVersion.
File renamed without changes.
+18 -14
View File
@@ -1,7 +1,4 @@
forge_support: true
timeout: 30000
online_mode: true
log_commands: false
server_connect_timeout: 5000
listeners:
- query_port: 25577
motd: '&4its a bungee cord surbur'
@@ -17,16 +14,8 @@ listeners:
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
remote_ping_cache: -1
network_compression_threshold: 256
disabled_commands: []
stats: bf8a54ea-2917-4b9e-b463-2c05bd7f1ac6
permissions:
default:
- bungeecord.command.server
@@ -36,6 +25,22 @@ permissions:
- bungeecord.command.end
- bungeecord.command.ip
- bungeecord.command.reload
log_pings: true
connection_throttle_limit: 3
prevent_proxy_connections: true
timeout: 30000
player_limit: -1
ip_forward: true
groups:
md_5:
- admin
remote_ping_timeout: 5000
connection_throttle: 1
log_commands: false
stats: bf8a54ea-2917-4b9e-b463-2c05bd7f1ac6
online_mode: true
forge_support: true
disabled_commands: []
servers:
s1:
motd: SERVER 1
@@ -45,4 +50,3 @@ servers:
motd: SERVER 2
address: localhost:25566
restricted: false
player_limit: -1
File renamed without changes.
+2
View File
@@ -0,0 +1,2 @@
rsa/
cached-votes.json
@@ -7,12 +7,12 @@ 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
debug: true
# All tokens, labeled by the serviceName of each server list.
tokens:
# Default token for all server lists, if another isn't supplied.
default: jqpli3gnd8l67qsg2d81vje09h
default: proxy1token
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
# option is currently not recommended as most voting sites only support the old protocol at present.
@@ -24,7 +24,7 @@ forwarding:
# - 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
method: pluginMessaging
pluginMessaging:
channel: nuvotifier:votes
@@ -46,11 +46,11 @@ forwarding:
# most set ups.
cache: file
# Only sends the vote to the server in which the player is currently joined.
onlySendToJoinedServer: false
onlySendToJoinedServer: true
#Sets which server to send the vote in the event the player is not online when the vote is cast
#If you do not want the vote forwarded to a fallback, set this value to empty ('')
#ONLY USED IF onlySendToJoinedServer is true!!
joinedServerFallback: 'Hub'
joinedServerFallback: 's1'
# Defines how quickly to dump votes over a player's connection when offloading a cache in votes per second
dumpRate: 5
# Options for file caching.
@@ -66,9 +66,9 @@ forwarding:
proxy:
Hub:
address: 127.0.0.1
port: 8193
token: MyKeyHere
port: 8194
token: server1token
Hub2:
address: 127.0.0.1
port: 8194
token: AnotherKeyHere
port: 8195
token: server2token
File renamed without changes.
+1
View File
@@ -0,0 +1 @@
../resources/waterfall.jar
View File
File renamed without changes.
@@ -1,8 +1,9 @@
use_netty_dns_resolver: true
disable_modern_tab_limiter: true
log_initial_handler_connections: true
allow_empty_packets: false
throttling:
tab_complete: 1000
game_version: ''
disable_modern_tab_limiter: true
use_netty_dns_resolver: true
disable_entity_metadata_rewrite: false
log_initial_handler_connections: true
log_server_list_ping: false
allow_empty_packets: false
+1
View File
@@ -0,0 +1 @@
logs/
File renamed without changes.
+2
View File
@@ -0,0 +1,2 @@
rsa/
cached-votes.json
+79
View File
@@ -0,0 +1,79 @@
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
host = "0.0.0.0"
# Port to listen for new votes on
port = 8193
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are
# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.
disable-v1-protocol = false
# All tokens, labeled by the serviceName of each server list.
[tokens]
# Default token for all server lists, if another isn't supplied.
default = "proxy2token"
# Configuration section for all vote forwarding to NuVotifier
[forwarding]
# Sets whether to set up a remote method for fowarding. Supported methods:
# - none - Does not set up a forwarding method.
# - pluginMessaging - Sets up plugin messaging.
# - proxy - Proxies votes to other NuVotifier servers from this server.
method = "pluginMessaging"
[forwarding.pluginMessaging]
channel = "nuvotifier:votes"
# Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
# to them. If excludedServers is not present, votes will be sent to all servers.
# If onlySendToJoinedServer is true and a player is on one of these servers, NuVotifier will process the vote as if the
# player was not on the network at all.
# excludedServers = ["server1", "server2"]
# If uncommented, turns excludedServers into an includedServers, where only servers on the list will be used.
# whitelist = true
# Sets which cache to use if there are no players on the server that the vote is being sent to. Supported caches:
# - none - Votes will simply be lost when no players are present on the server.
# - memory - Votes will be cached in memory when no players are present. When Bungee is restarted, the votes will be lost.
# - file - Votes will be saved to a local file every 3 minutes and on shutdown and reloaded when Bungee is restarted. This is suitable for
# most set ups.
cache = "file"
# Only sends the vote to the server in which the player is currently joined.
onlySendToJoinedServer = false
# Sets which server to send the vote in the event the player is not online when the vote is cast
# If you do not want the vote forwarded to a fallback, set this value to empty ('')
# ONLY USED IF onlySendToJoinedServer is true!!
joinedServerFallback = "s1"
# Defines how quickly to dump votes over a player's connection while offloading a cache in votes per second
dumpRate = 5
[forwarding.file-cache]
# Options for file caching.
name = "cached-votes.json"
# days before a vote is considered 'dead' - removed from cache with a console warning. -1 signifies no cache.
cacheTime = 10
[forwarding.memory-cache]
# Options for file caching.
name = "cached-votes.json"
# days before a vote is considered 'dead' - removed from cache with a console warning. -1 signifies no cache. Votes are
# also removed on restart
cacheTime = 10
[forwarding.proxy]
# Specify servers to proxy votes for.
[forwarding.proxy.Hub]
address = "127.0.0.1"
port = 8194
token = "server1token"
[forwarding.proxy.Hub2]
address = "127.0.0.1"
port = 8195
token = "server2token"
+1
View File
@@ -0,0 +1 @@
../resources/velocity.jar
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
cd $DIR
java -jar proxy.jar
+135
View File
@@ -0,0 +1,135 @@
# Config version. Do not change this
config-version = "1.0"
# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.
bind = "0.0.0.0:25578"
# What should be the MOTD? This gets displayed when the player adds your server to
# their server list. Legacy color codes and JSON are accepted.
motd = "&3doot doot imma velocity serbur"
# What should we display for the maximum number of players? (Velocity does not support a cap
# on the number of players online.)
show-max-players = 500
# Should we authenticate players with Mojang? By default, this is on.
online-mode = true
# If client's ISP/AS sent from this proxy is different from the one from Mojang's
# authentication server, the player is kicked. This disallows some VPN and proxy
# connections but is a weak form of protection.
prevent-client-proxy-connections = false
# Should we forward IP addresses and other data to backend servers?
# Available options:
# - "none": No forwarding will be done. All players will appear to be connecting
# from the proxy and will have offline-mode UUIDs.
# - "legacy": Forward player IPs and UUIDs in a BungeeCord-compatible format. Use this
# if you run servers using Minecraft 1.12 or lower.
# - "bungeeguard": Forward player IPs and UUIDs in a format supported by the BungeeGuard
# plugin. Use this if you run servers using Minecraft 1.12 or lower, and are
# unable to implement network level firewalling (on a shared host).
# - "modern": Forward player IPs and UUIDs as part of the login process using
# Velocity's native forwarding. Only applicable for Minecraft 1.13 or higher.
player-info-forwarding-mode = "LEGACY"
# If you are using modern or BungeeGuard IP forwarding, configure an unique secret here.
forwarding-secret = "not at all secret lolololol"
# Announce whether or not your server supports Forge. If you run a modded server, we
# suggest turning this on.
#
# If your network runs one modpack consistently, consider using ping-passthrough = "mods"
# instead for a nicer display in the server list.
announce-forge = false
# If enabled (default is false) and the proxy is in online mode, Velocity will kick
# any existing player who is online if a duplicate connection attempt is made.
kick-existing-players = false
# Should Velocity pass server list ping requests to a backend server?
# Available options:
# - "disabled": No pass-through will be done. The velocity.toml and server-icon.png
# will determine the initial server list ping response.
# - "mods": Passes only the mod list from your backend server into the response.
# The first server in your try list (or forced host) with a mod list will be
# used. If no backend servers can be contacted, Velocity won't display any
# mod information.
# - "description": Uses the description and mod list from the backend server. The first
# server in the try (or forced host) list that responds is used for the
# description and mod list.
# - "all": Uses the backend server's response as the proxy response. The Velocity
# configuration is used if no servers could be contacted.
ping-passthrough = "DISABLED"
[servers]
# Configure your servers here. Each key represents the server's name, and the value
# represents the IP address of the server to connect to.
s1 = "127.0.0.1:25565"
s2 = "127.0.0.1:25566"
# In what order we should try servers when a player logs in or is kicked from aserver.
try = [
"s1"
]
[forced-hosts]
# Configure your forced hosts here.
[advanced]
# How large a Minecraft packet has to be before we compress it. Setting this to zero will
# compress all packets, and setting it to -1 will disable compression entirely.
compression-threshold = 256
# How much compression should be done (from 0-9). The default is -1, which uses the
# default level of 6.
compression-level = -1
# How fast (in milliseconds) are clients allowed to connect after the last connection? By
# default, this is three seconds. Disable this by setting this to 0.
login-ratelimit = 3000
# Specify a custom timeout for connection timeouts here. The default is five seconds.
connection-timeout = 5000
# Specify a read timeout for connections here. The default is 30 seconds.
read-timeout = 30000
# Enables compatibility with HAProxy.
proxy-protocol = false
# Enables TCP fast open support on the proxy. Requires the proxy to run on Linux.
tcp-fast-open = false
# Enables BungeeCord plugin messaging channel support on Velocity.
bungee-plugin-message-channel = true
# Shows ping requests to the proxy from clients.
show-ping-requests = false
[query]
# Whether to enable responding to GameSpy 4 query responses or not.
enabled = false
# If query is enabled, on what port should the query protocol listen on?
port = 25577
# This is the map name that is reported to the query services.
map = "Velocity"
# Whether plugins should be shown in query response by default or not
show-plugins = false
[metrics]
# Whether metrics will be reported to bStats (https://bstats.org).
# bStats collects some basic information, like how many people use Velocity and their
# player count. We recommend keeping bStats enabled, but if you're not comfortable with
# this, you can turn this setting off. There is no performance penalty associated with
# having metrics enabled, and data sent to bStats can't identify your server.
enabled = true
# A unique, anonymous ID to identify this proxy with.
id = "729fe923-b383-4e5b-b456-5adf554bfe69"
log-failure = false
-1
View File
@@ -1 +0,0 @@
rsa/
-1
View File
@@ -1 +0,0 @@
../resources/proxy.jar
+1 -1
View File
@@ -7,4 +7,4 @@ 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
java -DIReallyKnowWhatIAmDoingISwear -Dcom.mojang.eula.agree=true -jar paper.jar nogui
+5
View File
@@ -13,6 +13,7 @@
settings:
minimum-api: none
allow-end: true
warn-on-overload: true
permissions-file: permissions.yml
@@ -23,6 +24,7 @@ settings:
deprecated-verbose: default
shutdown-message: Server closed
spawn-limits:
water-ambient: 20
monsters: 70
animals: 10
water-animals: 15
@@ -31,6 +33,9 @@ chunk-gc:
period-in-ticks: 600
load-threshold: 0
ticks-per:
water-ambient-spawns: 1
water-spawns: 1
ambient-spawns: 1
animal-spawns: 400
monster-spawns: 1
autosave: 6000
+52 -6
View File
@@ -11,8 +11,11 @@
# Docs: https://paper.readthedocs.org/
verbose: false
config-version: 16
config-version: 20
settings:
max-joins-per-tick: 3
console-has-all-permissions: false
chunk-tasks-per-tick: 1000
load-permissions-yml-before-plugins: true
bungee-online-mode: true
use-versioned-world: false
@@ -41,10 +44,20 @@ settings:
max-player-auto-save-per-tick: -1
async-chunks:
enable: true
generation: true
thread-per-world-generation: true
load-threads: -1
threads: -1
use-alternative-luck-formula: false
unsupported-settings:
allow-headless-pistons: false
allow-permanent-block-break-exploits: false
allow-piston-duplication: false
allow-permanent-block-break-exploits-readme: This setting controls if players
should be able to break bedrock, end portals and other intended to be permanent
blocks.
allow-piston-duplication-readme: This setting controls if player should be able
to use TNT duplication, but this also allows duplicating carpet, rails and potentially
other items
allow-headless-pistons-readme: This setting controls if players should be able
to create headless pistons.
messages:
no-permission: '&cI''m sorry, but you do not have permission to perform this command.
Please contact the server administrators if you believe that this is in error.'
@@ -54,6 +67,7 @@ messages:
flying-vehicle: Flying is not enabled on this server
connection-throttle: Connection throttled! Please wait before reconnecting.
timings:
server-name: Unknown Server
enabled: true
verbose: true
server-name-privacy: false
@@ -64,8 +78,24 @@ timings:
history-length: 3600
world-settings:
default:
zombies-target-turtle-eggs: true
per-player-mob-spawns: false
phantoms-do-not-spawn-on-creative-players: true
phantoms-only-attack-insomniacs: true
light-queue-size: 20
count-all-mobs-for-spawning: false
seed-based-feature-search: true
nether-ceiling-void-damage-height: 0
fix-zero-tick-instant-grow-farms: true
portal-create-radius: 16
iron-golems-can-spawn-in-air: false
entities-target-with-follow-range: false
zombie-villager-infection-chance: -1.0
portal-search-radius: 128
game-mechanics:
disable-pillager-patrols: false
nerf-pigmen-from-nether-portals: false
disable-relative-projectile-velocity: false
villages-load-chunks: false
scan-for-legacy-ender-dragon: true
disable-chest-cat-detection: false
@@ -74,6 +104,14 @@ world-settings:
disable-sprint-interruption-on-attack: false
shield-blocking-delay: 5
disable-unloaded-chunk-enderpearl-exploit: true
pillager-patrols:
spawn-chance: 0.2
spawn-delay:
per-player: false
ticks: 12000
start:
per-player: false
day: 5
queue-light-updates: false
optimize-explosions: false
disable-teleportation-suffocation-check: false
@@ -141,13 +179,12 @@ world-settings:
- chest
- diamond_ore
- redstone_ore
- lit_redstone_ore
- clay
- emerald_ore
- ender_chest
replacement-blocks:
- stone
- planks
- oak_planks
skip-entity-ticking-in-chunks-scheduled-for-unload: true
armor-stands-do-collision-entity-lookups: true
keep-spawn-loaded-range: 8
@@ -176,3 +213,12 @@ world-settings:
mob-spawner-tick-rate: 1
squid-spawn-height:
maximum: 0.0
alt-item-despawn-rate:
enabled: false
items:
COBBLESTONE: 300
viewdistances:
no-tick-view-distance: -1
generator-settings:
flat-bedrock: false
baby-zombie-movement-modifier: 0.5
+1 -1
View File
@@ -3,7 +3,7 @@
host: 0.0.0.0
# Port to listen for new votes on
port: 8193
port: 8194
# Whether or not to print debug messages. In a production system, this should be set to false.
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
+1
View File
@@ -0,0 +1 @@
../../../universal/build/libs/nuvotifier.jar
+19 -6
View File
@@ -1,26 +1,36 @@
#Minecraft server properties
#Fri Dec 21 15:15:00 EST 2018
#Thu Sep 03 15:49:51 EDT 2020
spawn-protection=16
max-tick-time=60000
query.port=25565
generator-settings=
sync-chunk-writes=true
force-gamemode=false
allow-nether=true
enforce-whitelist=false
gamemode=0
gamemode=survival
broadcast-console-to-ops=true
enable-query=false
player-idle-timeout=0
difficulty=1
difficulty=easy
spawn-monsters=true
broadcast-rcon-to-ops=true
op-permission-level=4
pvp=true
entity-broadcast-range-percentage=100
snooper-enabled=true
level-type=DEFAULT
level-type=default
hardcore=false
enable-status=true
enable-command-block=false
max-players=20
network-compression-threshold=256
resource-pack-sha1=
max-world-size=29999984
function-permission-level=2
rcon.port=25575
server-port=25565
debug=false
server-ip=
spawn-npcs=true
allow-flight=false
@@ -29,11 +39,14 @@ view-distance=10
resource-pack=
spawn-animals=true
white-list=false
rcon.password=
generate-structures=true
online-mode=false
max-build-height=256
online-mode=false
level-seed=
prevent-proxy-connections=false
use-native-transport=true
motd=A Minecraft Server
enable-jmx-monitoring=false
rate-limit=0
enable-rcon=false
motd=A Minecraft Server
+37 -2
View File
@@ -10,8 +10,9 @@
# IRC: #spigot @ irc.spi.gt ( http://www.spigotmc.org/pages/irc/ )
# Forums: http://www.spigotmc.org/
config-version: 11
config-version: 12
settings:
log-villager-deaths: true
debug: false
save-user-cache-on-stop-only: false
sample-count: 12
@@ -60,6 +61,16 @@ advancements:
- minecraft:story/disabled
world-settings:
default:
seed-bastion: 30084232
seed-fortress: 30084232
seed-mansion: 10387319
seed-fossil: 14357921
seed-portal: 34222645
seed-outpost: 165745296
seed-endcity: 10387313
view-distance: default
trident-despawn-rate: 1200
end-portal-sound-radius: 0
verbose: true
merge-radius:
exp: 3.0
@@ -91,6 +102,12 @@ world-settings:
min: 45.0
mob-spawn-range: 8
growth:
beetroot-modifier: 100
carrot-modifier: 100
potato-modifier: 100
bamboo-modifier: 100
sweetberry-modifier: 100
kelp-modifier: 100
cactus-modifier: 100
cane-modifier: 100
melon-modifier: 100
@@ -102,11 +119,30 @@ world-settings:
vine-modifier: 100
cocoa-modifier: 100
entity-activation-range:
raiders: 48
villagers: 32
flying-monsters: 32
villagers-work-immunity-after: 100
villagers-work-immunity-for: 20
villagers-active-for-panic: true
animals: 32
monsters: 32
misc: 16
water: 16
tick-inactive-villagers: true
wake-up-inactive:
animals-max-per-tick: 4
animals-every: 1200
animals-for: 100
monsters-max-per-tick: 8
monsters-every: 400
monsters-for: 100
villagers-max-per-tick: 4
villagers-every: 600
villagers-for: 100
flying-monsters-max-per-tick: 8
flying-monsters-every: 200
flying-monsters-for: 100
entity-tracking-range:
players: 48
animals: 48
@@ -120,7 +156,6 @@ world-settings:
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
+2 -4
View File
@@ -3,17 +3,15 @@ banned-players.json
cache/
eula.txt
logs/
permissions.yml
ops.json
usercache.json
usernamecache.json
version_history.json
whitelist.json
world/
world_nether/
world_the_end/
.mixin.out
-37
View File
@@ -1,37 +0,0 @@
# This is the main configuration file for Bukkit.
# As you can see, there's actually not that much to configure without any plugins.
# For a reference for any variable inside this file, check out the Bukkit Wiki at
# https://www.spigotmc.org/go/bukkit-yml
#
# If you need help on this file, feel free to join us on irc or leave a message
# on the forums asking for advice.
#
# IRC: #spigot @ irc.spi.gt
# (If this means nothing to you, just go to https://www.spigotmc.org/go/irc )
# Forums: https://www.spigotmc.org/
# Bug tracker: https://www.spigotmc.org/go/bugs
settings:
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
-17
View File
@@ -1,17 +0,0 @@
# This is the commands configuration file for Bukkit.
# For documentation on how to make use of this file, check out the Bukkit Wiki at
# https://www.spigotmc.org/go/commands-yml
#
# If you need help on this file, feel free to join us on irc or leave a message
# on the forums asking for advice.
#
# IRC: #spigot @ irc.spi.gt
# (If this means nothing to you, just go to https://www.spigotmc.org/go/irc )
# Forums: https://www.spigotmc.org/
# Bug tracker: https://www.spigotmc.org/go/bugs
command-block-overrides: []
ignore-vanilla-permissions: false
aliases:
icanhasbukkit:
- version $1-
+1
View File
@@ -0,0 +1 @@
rsa/
+8
View File
@@ -0,0 +1,8 @@
host: 127.0.0.1
port: 8195
debug: true
disable-v1-protocol: false
tokens: {default: server2token}
forwarding:
method: pluginMessaging
pluginMessaging: {channel: 'nuvotifier:votes'}
Loaded 100 of 163 files, more files were not shown because too many files have changed in this diff. Show more