Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cb4a1dc54
|
||
|
|
9ea469dbd7
|
||
|
|
011bc26a1a | ||
|
|
c697099eeb | ||
|
|
4a90995589 | ||
|
|
6a98e18a38 | ||
|
|
fd859d839d | ||
|
|
6085ffbcd3 | ||
|
|
53bb8c938b | ||
|
|
b771bcb095 | ||
|
|
fdb1b65159 | ||
|
|
bf0de0a879 | ||
|
|
6bdb2edb66 | ||
|
|
4a89704de6 | ||
|
|
999c9cd849 | ||
|
|
f41f0e6fef | ||
|
|
a3a73d9466 | ||
|
|
ed1e368aae | ||
|
|
4df84efdcd | ||
|
|
95fc2368a5 | ||
|
|
9d69da2dab | ||
|
|
65007e248e | ||
|
|
f1f564d67a | ||
|
|
f80de12ae2 | ||
|
|
acb19a520a | ||
|
|
9c27071213 | ||
|
|
0f6020818d | ||
|
|
fcf6c93404 | ||
|
|
3d0f255ab4 | ||
|
|
ede5cae333 | ||
|
|
b8f9cb937a | ||
|
|
1fae11c0ff | ||
|
|
41eb6327e6 | ||
|
|
c723fc7eac | ||
|
|
7812823367 | ||
|
|
2d5c2c78d2 | ||
|
|
6e64890c04 | ||
|
|
d269e9da6b | ||
|
|
5466876f41 | ||
|
|
4003006fa4 | ||
|
|
13d0cb1d57 | ||
|
|
15ad7d5abc | ||
|
|
8966fbf3d8 | ||
|
|
616d25683f | ||
|
|
669c8a769e | ||
|
|
7b10eca263 | ||
|
|
d22f4cd58b | ||
|
|
8ba7e87067 | ||
|
|
5776acaf34 | ||
|
|
90f6ed8b55 | ||
|
|
616bd4ad01 | ||
|
|
448baf0329 | ||
|
|
13fe852bcd | ||
|
|
c996e36b97 | ||
|
|
ad9e6f5fe8 | ||
|
|
53eb9c8fec | ||
|
|
6dbda167d3 | ||
|
|
be9f92d5cd | ||
|
|
96bf715fbc | ||
|
|
628273413b | ||
|
|
bbfa9e4aa9 | ||
|
|
a34b07f2d1 | ||
|
|
1d90a59d5a | ||
|
|
2734836e58 | ||
|
|
768daf099f | ||
|
|
b6292667b8 | ||
|
|
8519535566 | ||
|
|
08e907678f | ||
|
|
27b93ac045 | ||
|
|
97f4c91c24 | ||
|
|
e919c845f7 | ||
|
|
1fb35276f7 | ||
|
|
f5f596f21c | ||
|
|
88523db213 | ||
|
|
3599b0fa1b | ||
|
|
950b543e98 | ||
|
|
e66927dd1a | ||
|
|
98d4f69ffb | ||
|
|
ad44c76309 | ||
|
|
58de66699e | ||
|
|
b81052327d | ||
|
|
59a3eff421 | ||
|
|
411a529e18 | ||
|
|
4d6946ab2c | ||
|
|
d5ae57b28e | ||
|
|
aa1d9b3d71 | ||
|
|
c250048bf7 | ||
|
|
1db78a5bea | ||
|
|
2ad0fa118f | ||
|
|
38afe59db0 | ||
|
|
b173930c14 | ||
|
|
61c4529df6 | ||
|
|
dd48af5b50 | ||
|
|
bc3c667d2a | ||
|
|
1041bdb6b7 | ||
|
|
fc7a04547c | ||
|
|
dd2a1c4ed5 | ||
|
|
c7667abf24 | ||
|
|
df484523c2 | ||
|
|
81fca5b218 | ||
|
|
c520fd6650 | ||
|
|
e71ff635e3 | ||
|
|
4d52ef42e4 | ||
|
|
c21d18594d | ||
|
|
9944bb7077 | ||
|
|
7d43b7399f | ||
|
|
5d797065cb | ||
|
|
43c8489574 | ||
|
|
348dbca900 | ||
|
|
395c63a80b | ||
|
|
99afd0ca71 | ||
|
|
2243be4954 | ||
|
|
8a73b0bf9c | ||
|
|
595b7ed75f | ||
|
|
759b7a886b | ||
|
|
eb68085746 | ||
|
|
16b093e4d6 | ||
|
|
a2848e43d0 | ||
|
|
1bfa28af48 | ||
|
|
b745236131 | ||
|
|
4330d41286 | ||
|
|
75228868f8 | ||
|
|
671c6e17a9 | ||
|
|
c3030d56e8 | ||
|
|
9fe4d6b9c3 | ||
|
|
99dbaa703a | ||
|
|
0fa42b44cc | ||
|
|
7d75bce02c | ||
|
|
60923598b9 | ||
|
|
0c6dc99bcc | ||
|
|
f5776dd1cb | ||
|
|
7134c12826 | ||
|
|
8cc93daf70 | ||
|
|
98dfc345e9 | ||
|
|
590b7618f3 | ||
|
|
4cd2ce55fc | ||
|
|
9fe98a92a9 | ||
|
|
c72972c286 | ||
|
|
92a122eaf8 | ||
|
|
67117b38e3 | ||
|
|
9255062aff | ||
|
|
fe45e48447 | ||
|
|
0391e9e4bf | ||
|
|
59fa4857e3 | ||
|
|
4d663737c1 | ||
|
|
8380ffce9a | ||
|
|
b9e648bba8 | ||
|
|
c34551e630 | ||
|
|
d931cc2464 | ||
|
|
07f81eb781 | ||
|
|
c0ef61c819 | ||
|
|
09bbfa8629 | ||
|
|
aef038ef3a | ||
|
|
ac3827625d | ||
|
|
acc092f955 | ||
|
|
b494e1dc9d | ||
|
|
27a368bd08 | ||
|
|
3c5b533a44 | ||
|
|
f569c725d0 | ||
|
|
a09bc5a131 | ||
|
|
c4e4f6e416 | ||
|
|
6ed181e835 |
No files matched your search
-16
@@ -1,16 +0,0 @@
|
||||
pipeline:
|
||||
build:
|
||||
image: cogniteev/oracle-java
|
||||
commands: ./gradlew build test --no-daemon
|
||||
publish:
|
||||
image: cogniteev/oracle-java
|
||||
commands:
|
||||
- ./gradlew publishversionedPublicationToParallelBlockRepository
|
||||
- ./gradlew publishLatestToParallelBlockRaw
|
||||
environment:
|
||||
- PUBLISH=yes
|
||||
- LATEST_BUILD=yes
|
||||
secrets: [SNAPSHOT_REPO, RELEASE_REPO, REPO_USERNAME, REPO_PASSWORD, RAW_UPLOAD_PATH]
|
||||
when:
|
||||
branch: master
|
||||
event: push
|
||||
@@ -0,0 +1 @@
|
||||
.gitea/CODEOWNERS linguist-detectable=false
|
||||
@@ -0,0 +1,3 @@
|
||||
# Pull requests request review from the eligible staff team(s).
|
||||
# One approval from either an Admin or Developer satisfies branch protection.
|
||||
* @trueog/Admins @trueog/Developers
|
||||
+18
@@ -7,6 +7,20 @@
|
||||
# netbeans
|
||||
/nbproject
|
||||
|
||||
# eclipse
|
||||
api/.classpath
|
||||
api/.project
|
||||
api/.settings/
|
||||
bukkit/.classpath
|
||||
bukkit/.project
|
||||
bukkit/.settings/
|
||||
common/.classpath
|
||||
common/.project
|
||||
common/.settings/
|
||||
universal/.classpath
|
||||
universal/.project
|
||||
universal/.settings/
|
||||
|
||||
# we use maven!
|
||||
/build.xml
|
||||
|
||||
@@ -46,3 +60,7 @@ debug.txt
|
||||
|
||||
# gradle stuff
|
||||
.gradle/
|
||||
.gradletasknamecache
|
||||
|
||||
# fabric
|
||||
fabric/run/
|
||||
@@ -1,35 +1,11 @@
|
||||
# NuVotifier
|
||||
# Votifier-OG
|
||||
|
||||
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.
|
||||
Votifier-OG is a 1.19.4 fork of NuVotifier, which is a secure alternative to using the original Votifier project.
|
||||
Votifier-OG will work in place of NuVotifier, and will work in place of Votifier - any vote listener that supports
|
||||
Votifier will also support NuVotifier and Votifier-OG.
|
||||
|
||||
## Why fork Votifier?
|
||||
|
||||
Votifier has lagged in activity, has severe security flaws in its protocol, and
|
||||
is vulnerable to denial of service attacks.
|
||||
|
||||
## Is NuVotifier a drop-in replacement for Votifier?
|
||||
|
||||
Yes! Except for some minor non-breaking changes (look into the [technical qa
|
||||
docs](docs/technical_qa.md) for more info), NuVotifier is 100% compatible with
|
||||
the old Votifier with many more features.
|
||||
|
||||
# Documentation / Configuration Guide
|
||||
|
||||
All documentation is located under the [docs](docs/) directory. A server admin
|
||||
configuration guide is available [here](docs/usage.md).
|
||||
|
||||
# Support
|
||||
|
||||
Feel free to join the MOSS Discord server using the below link:
|
||||
|
||||
[](https://discord.gg/anUtuAC)
|
||||
Votifier-OG supports Purpur 1.19.4. It is maintained by [TrueOG Network](https://trueog.net/)
|
||||
|
||||
# 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).
|
||||
@@ -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!
|
||||
+44
-24
@@ -20,6 +20,8 @@ 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 +50,36 @@ 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() {}
|
||||
|
||||
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) {
|
||||
@@ -79,19 +91,25 @@ public class Vote {
|
||||
}
|
||||
|
||||
public Vote(JsonObject jsonObject) {
|
||||
this(jsonObject.get("serviceName").getAsString(),
|
||||
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 +189,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 +203,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
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# Fetch all submodule content.
|
||||
git submodule update --force --recursive --init
|
||||
@@ -1,34 +0,0 @@
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
allprojects {
|
||||
group 'com.vexsoftware'
|
||||
version '2.3.7'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://hub.spigotmc.org/nexus/content/groups/public/'
|
||||
}
|
||||
maven {
|
||||
url "https://oss.sonatype.org/content/groups/public/"
|
||||
}
|
||||
maven {
|
||||
url "http://repo.spongepowered.org/maven"
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
filter(ReplaceTokens, tokens: ["app.version": this.project.version])
|
||||
}
|
||||
}
|
||||
|
||||
if (System.env.PUBLISH) {
|
||||
apply from: 'publish.gradle'
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
import org.gradle.kotlin.dsl.provideDelegate
|
||||
|
||||
/* ------------------------------ Plugins ------------------------------ */
|
||||
plugins {
|
||||
id("java") // Import Java plugin.
|
||||
id("java-library") // Import Java Library plugin.
|
||||
id("com.diffplug.spotless") version "8.1.0" // Import Spotless plugin.
|
||||
id("com.gradleup.shadow") version "8.3.9" // Import Shadow plugin.
|
||||
id("checkstyle") // Import Checkstyle plugin.
|
||||
eclipse // Import Eclipse plugin.
|
||||
kotlin("jvm") version "2.1.21" // Import Kotlin JVM plugin.
|
||||
}
|
||||
|
||||
extra["kotlinAttribute"] = Attribute.of("kotlin-tag", Boolean::class.javaObjectType)
|
||||
|
||||
val kotlinAttribute: Attribute<Boolean> by rootProject.extra
|
||||
|
||||
/* --------------------------- JDK / Kotlin ---------------------------- */
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17 // Compile with JDK 17 compatibility.
|
||||
toolchain { // Select Java toolchain.
|
||||
languageVersion.set(JavaLanguageVersion.of(17)) // Use JDK 17.
|
||||
vendor.set(JvmVendorSpec.GRAAL_VM) // Use GraalVM CE.
|
||||
}
|
||||
}
|
||||
|
||||
kotlin { jvmToolchain(17) }
|
||||
|
||||
/* ----------------------------- Metadata ------------------------------ */
|
||||
group = "net.trueog.votifier-og" // Declare bundle identifier.
|
||||
|
||||
version = "1.0.2" // Declare plugin version (will be in .jar).
|
||||
|
||||
val apiVersion = "1.19" // Declare minecraft server target version.
|
||||
|
||||
/* ----------------------------- Resources ----------------------------- */
|
||||
tasks.named<ProcessResources>("processResources") {
|
||||
val props = mapOf("version" to version, "apiVersion" to apiVersion)
|
||||
inputs.properties(props) // Indicates to rerun if version changes.
|
||||
filesMatching("plugin.yml") { expand(props) }
|
||||
from("LICENSE") { into("/") } // Bundle licenses into jarfiles.
|
||||
}
|
||||
|
||||
/* ---------------------------- Repos ---------------------------------- */
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral() // Import the Maven Central Maven Repository.
|
||||
gradlePluginPortal() // Import the Gradle Plugin Portal Maven Repository.
|
||||
maven { url = uri("https://repo.purpurmc.org/snapshots") } // Import the PurpurMC Maven Repository.
|
||||
maven { url = uri("file://${System.getProperty("user.home")}/.m2/repository") }
|
||||
System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let { // TrueOG Bootstrap mavenLocal().
|
||||
val dir = file(it)
|
||||
if (dir.isDirectory) {
|
||||
println("Using SELF_MAVEN_LOCAL_REPO at: $it")
|
||||
maven { url = uri("file://${dir.absolutePath}") }
|
||||
} else {
|
||||
logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
?: run {
|
||||
logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------- Java project deps ---------------------------- */
|
||||
configurations.create("shadeOnly")
|
||||
|
||||
dependencies { implementation(project(":nuvotifier-bukkit")) }
|
||||
|
||||
apply(from = "eclipse.gradle.kts") // Import eclipse classpath support script.
|
||||
|
||||
/* ---------------------- Reproducible jars ---------------------------- */
|
||||
allprojects {
|
||||
tasks.withType<AbstractArchiveTask>().configureEach { // Ensure reproducible .jars
|
||||
isPreserveFileTimestamps = false
|
||||
isReproducibleFileOrder = true
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------- Shadow -------------------------------- */
|
||||
tasks.shadowJar {
|
||||
exclude("io.github.miniplaceholders.*") // Exclude the MiniPlaceholders package from being shadowed.
|
||||
isEnableRelocation = true
|
||||
relocationPrefix = "${project.group}.shadow"
|
||||
archiveClassifier.set("") // Use empty string instead of null.
|
||||
}
|
||||
|
||||
tasks.jar { archiveClassifier.set("part") } // Applies to root jarfile only.
|
||||
|
||||
val copyBukkitJarToRoot by tasks.registering(Copy::class)
|
||||
|
||||
project(":nuvotifier-bukkit").pluginManager.withPlugin("com.gradleup.shadow") {
|
||||
val bukkitShadowJarProvider = project(":nuvotifier-bukkit").tasks.named<Jar>("shadowJar")
|
||||
copyBukkitJarToRoot.configure {
|
||||
dependsOn(bukkitShadowJarProvider)
|
||||
from(bukkitShadowJarProvider.flatMap { it.archiveFile })
|
||||
into(layout.buildDirectory.dir("libs"))
|
||||
rename { "Votifier-OG-${project.version}.jar" }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
dependsOn(tasks.spotlessApply, tasks.shadowJar, copyBukkitJarToRoot)
|
||||
} // Build depends on spotless and shadow.
|
||||
|
||||
tasks.shadowJar { finalizedBy(copyBukkitJarToRoot) }
|
||||
|
||||
/* --------------------------- Javac opts ------------------------------- */
|
||||
allprojects {
|
||||
tasks.withType<JavaCompile>().configureEach {
|
||||
options.compilerArgs.add("-parameters") // Enable reflection for java code.
|
||||
options.isFork = true // Run javac in its own process.
|
||||
options.compilerArgs.add("-Xlint:deprecation") // Trigger deprecation warning messages.
|
||||
options.encoding = "UTF-8" // Use UTF-8 file encoding.
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------- Auto Formatting ------------------------ */
|
||||
spotless {
|
||||
java {
|
||||
eclipse().configFile("config/formatter/eclipse-java-formatter.xml") // Eclipse java formatting.
|
||||
leadingTabsToSpaces() // Convert leftover leading tabs to spaces.
|
||||
removeUnusedImports() // Remove imports that aren't being called.
|
||||
}
|
||||
kotlinGradle {
|
||||
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } // JetBrains Kotlin formatting.
|
||||
target("build.gradle.kts", "settings.gradle.kts") // Gradle files to format.
|
||||
}
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.18.1" // Declare checkstyle version to use.
|
||||
configFile = file("config/checkstyle/checkstyle.xml") // Point checkstyle to config file.
|
||||
isIgnoreFailures = true // Don't fail the build if checkstyle does not pass.
|
||||
isShowViolations = true // Show the violations in any IDE with the checkstyle plugin.
|
||||
}
|
||||
|
||||
tasks.named("compileJava") {
|
||||
dependsOn("spotlessApply") // Run spotless before compiling with the JDK.
|
||||
}
|
||||
|
||||
tasks.named("spotlessCheck") {
|
||||
dependsOn("spotlessApply") // Run spotless before checking if spotless ran.
|
||||
}
|
||||
|
||||
/* ------------------------------ Eclipse SHIM ------------------------- */
|
||||
|
||||
// This can't be put in eclipse.gradle.kts because Gradle is weird.
|
||||
subprojects {
|
||||
apply(plugin = "java-library")
|
||||
apply(plugin = "eclipse")
|
||||
eclipse.project.name = "${project.name}-${rootProject.name}"
|
||||
tasks.withType<Jar>().configureEach { archiveBaseName.set("${project.name}-${rootProject.name}") }
|
||||
}
|
||||
|
||||
/* ----------------------- Subproject configuration -------------------- */
|
||||
|
||||
subprojects {
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(17))
|
||||
vendor.set(JvmVendorSpec.GRAAL_VM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project(":nuvotifier-api") {
|
||||
dependencies {
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
}
|
||||
tasks.test { useJUnit() }
|
||||
}
|
||||
|
||||
project(":nuvotifier-common") {
|
||||
dependencies {
|
||||
api(project(":nuvotifier-api"))
|
||||
implementation(platform("io.netty:netty-bom:4.1.110.Final"))
|
||||
implementation("io.netty:netty-handler")
|
||||
implementation("io.netty:netty-transport-native-epoll:4.1.110.Final:linux-x86_64")
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
}
|
||||
}
|
||||
|
||||
project(":nuvotifier-bukkit") {
|
||||
apply(plugin = "com.gradleup.shadow")
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT")
|
||||
implementation(project(":nuvotifier-api"))
|
||||
implementation(project(":nuvotifier-common"))
|
||||
}
|
||||
|
||||
tasks.named<ProcessResources>("processResources") {
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
val props =
|
||||
mapOf(
|
||||
"version" to rootProject.version,
|
||||
"apiVersion" to apiVersion,
|
||||
"internalVersion" to rootProject.version,
|
||||
)
|
||||
inputs.properties(props)
|
||||
filesMatching("plugin.yml") { expand(props) }
|
||||
from(rootProject.file("LICENSE")) { into("/") }
|
||||
}
|
||||
|
||||
tasks.named<Jar>("jar") { manifest { attributes("Implementation-Version" to project.version) } }
|
||||
|
||||
tasks.named<Jar>("shadowJar") {
|
||||
archiveBaseName.set("Votifier-OG")
|
||||
archiveVersion.set(rootProject.version.toString())
|
||||
archiveClassifier.set("")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/bin/
|
||||
@@ -1,5 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
class BukkitScheduler implements VotifierScheduler {
|
||||
private final NuVotifierBukkit plugin;
|
||||
|
||||
public BukkitScheduler(NuVotifierBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private int toTicks(int time, TimeUnit unit) {
|
||||
return (int) (unit.toMillis(time) / 50);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(
|
||||
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, toTicks(delay, unit)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(plugin.getServer()
|
||||
.getScheduler()
|
||||
.runTaskTimerAsynchronously(plugin, runnable, toTicks(delay, unit), toTicks(repeat, unit)));
|
||||
}
|
||||
|
||||
private static class BukkitTaskWrapper implements ScheduledVotifierTask {
|
||||
private final BukkitTask task;
|
||||
|
||||
private BukkitTaskWrapper(BukkitTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,44 +18,39 @@
|
||||
|
||||
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.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.forwarding.ForwardingVoteSink;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.model.VotifierEvent;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSink;
|
||||
import com.vexsoftware.votifier.util.IOUtil;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.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.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* The main Votifier plugin class.
|
||||
@@ -66,24 +61,9 @@ import java.util.logging.Level;
|
||||
public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, VotifierPlugin, ForwardedVoteListener {
|
||||
|
||||
/**
|
||||
* The Votifier instance.
|
||||
* The server bootstrap.
|
||||
*/
|
||||
private static NuVotifierBukkit instance;
|
||||
|
||||
/**
|
||||
* The current Votifier version.
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
@@ -101,22 +81,30 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
private Map<String, Key> tokens = new HashMap<>();
|
||||
|
||||
private ForwardingVoteSink forwardingMethod;
|
||||
private VotifierScheduler scheduler;
|
||||
private LoggingAdapter pluginLogger;
|
||||
private boolean isFolia;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
NuVotifierBukkit.instance = this;
|
||||
private boolean loadAndBind() {
|
||||
try {
|
||||
Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler");
|
||||
isFolia = true;
|
||||
|
||||
// Set the plugin version.
|
||||
version = getDescription().getVersion();
|
||||
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.
|
||||
File config = new File(getDataFolder() + "/config.yml");
|
||||
YamlConfiguration cfg;
|
||||
File rsaDirectory = new File(getDataFolder() + "/rsa");
|
||||
File config = new File(getDataFolder(), "config.yml");
|
||||
|
||||
/*
|
||||
* Use IP address from server.properties as a default for
|
||||
@@ -125,8 +113,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
* assigned to the server.
|
||||
*/
|
||||
String hostAddr = Bukkit.getServer().getIp();
|
||||
if (hostAddr == null || hostAddr.length() == 0)
|
||||
hostAddr = "0.0.0.0";
|
||||
if (hostAddr == null || hostAddr.length() == 0) hostAddr = "0.0.0.0";
|
||||
|
||||
/*
|
||||
* Create configuration file if it does not exists; otherwise, load it
|
||||
@@ -137,13 +124,19 @@ 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.write(cfgStr, config, StandardCharsets.UTF_8);
|
||||
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
|
||||
@@ -161,11 +154,13 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "Error creating configuration file", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
YamlConfiguration cfg;
|
||||
File rsaDirectory = new File(getDataFolder(), "rsa");
|
||||
|
||||
// Load configuration.
|
||||
cfg = YamlConfiguration.loadConfiguration(config);
|
||||
|
||||
@@ -175,28 +170,36 @@ 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 {
|
||||
keyPair = RSAIO.load(rsaDirectory);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error reading configuration file or RSA tokens", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
getLogger().log(Level.SEVERE, "Error reading configuration file or RSA tokens", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
debug = cfg.getBoolean("debug", false);
|
||||
// the quiet flag always runs priority to the debug flag
|
||||
if (cfg.isBoolean("quiet")) {
|
||||
debug = !cfg.getBoolean("quiet");
|
||||
} else {
|
||||
// otherwise, default to being noisy
|
||||
debug = cfg.getBoolean("debug", true);
|
||||
}
|
||||
|
||||
// Load Votifier tokens.
|
||||
// Load Votifier tokens.
|
||||
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
|
||||
|
||||
if (tokenSection != null) {
|
||||
Map<String, Object> websites = tokenSection.getValues(false);
|
||||
for (Map.Entry<String, Object> website : websites.entrySet()) {
|
||||
tokens.put(website.getKey(), KeyCreator.createKeyFrom(website.getValue().toString()));
|
||||
tokens.put(
|
||||
website.getKey(),
|
||||
KeyCreator.createKeyFrom(website.getValue().toString()));
|
||||
getLogger().info("Loaded token for website: " + website.getKey());
|
||||
}
|
||||
} else {
|
||||
@@ -207,10 +210,8 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
try {
|
||||
cfg.save(config);
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error generating Votifier token", e);
|
||||
gracefulExit();
|
||||
return;
|
||||
getLogger().log(Level.SEVERE, "Error generating Votifier token", e);
|
||||
return false;
|
||||
}
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
|
||||
@@ -223,8 +224,8 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
// Initialize the receiver.
|
||||
final String host = cfg.getString("host", hostAddr);
|
||||
final int port = cfg.getInt("port", 8192);
|
||||
if (debug)
|
||||
getLogger().info("DEBUG mode enabled!");
|
||||
if (!debug) getLogger().info("QUIET mode enabled!");
|
||||
|
||||
if (port >= 0) {
|
||||
final boolean disablev1 = cfg.getBoolean("disable-v1-protocol");
|
||||
if (disablev1) {
|
||||
@@ -234,37 +235,8 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
serverGroup = new NioEventLoopGroup(1);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(NuVotifierBukkit.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(NuVotifierBukkit.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
this.bootstrap = new VotifierServerBootstrap(host, port, this, disablev1);
|
||||
this.bootstrap.start(error -> {});
|
||||
} else {
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Your Votifier port is less than 0, so we assume you do NOT want to start the");
|
||||
@@ -275,57 +247,99 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
|
||||
ConfigurationSection forwardingConfig = cfg.getConfigurationSection("forwarding");
|
||||
if (forwardingConfig != null) {
|
||||
String method = forwardingConfig.getString("method", "none").toLowerCase(); //Default to lower case for case-insensitive searches
|
||||
String method = forwardingConfig
|
||||
.getString("method", "none")
|
||||
.toLowerCase(); // Default to lower case for case-insensitive searches
|
||||
if ("none".equals(method)) {
|
||||
getLogger().info("Method none selected for vote forwarding: Votes will not be received from a forwarder.");
|
||||
getLogger()
|
||||
.info("Method none selected for vote forwarding: Votes will not be received from a forwarder.");
|
||||
} else if ("pluginmessaging".equals(method)) {
|
||||
String channel = forwardingConfig.getString("pluginMessaging.channel", "NuVotifier");
|
||||
try {
|
||||
forwardingMethod = new BukkitPluginMessagingForwardingSink(this, channel, this);
|
||||
getLogger().info("Receiving votes over PluginMessaging channel '" + channel + "'.");
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
getLogger()
|
||||
.log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
} else {
|
||||
getLogger().severe("No vote forwarding method '" + method + "' known. Defaulting to noop implementation.");
|
||||
getLogger()
|
||||
.severe("No vote forwarding method '" + method + "' known. Defaulting to noop implementation.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void halt() {
|
||||
// Shut down the network handlers.
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
forwardingMethod = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
getCommand("nvreload").setExecutor(new NVReloadCmd(this));
|
||||
getCommand("testvote").setExecutor(new TestVoteCmd(this));
|
||||
|
||||
if (!loadAndBind()) {
|
||||
gracefulExit();
|
||||
setEnabled(false); // safer to just bomb out
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Shut down the network handlers.
|
||||
if (serverGroup != null) {
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
}
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
}
|
||||
halt();
|
||||
getLogger().info("Votifier disabled.");
|
||||
}
|
||||
|
||||
public boolean reload() {
|
||||
try {
|
||||
halt();
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "On halt, an exception was thrown. This may be fine!", ex);
|
||||
}
|
||||
|
||||
if (loadAndBind()) {
|
||||
getLogger().info("Reload was successful.");
|
||||
return true;
|
||||
} else {
|
||||
try {
|
||||
halt();
|
||||
getLogger()
|
||||
.log(
|
||||
Level.SEVERE,
|
||||
"On reload, there was a problem with the configuration. Votifier currently does nothing!");
|
||||
} catch (Exception ex) {
|
||||
getLogger()
|
||||
.log(
|
||||
Level.SEVERE,
|
||||
"On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!",
|
||||
ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void gracefulExit() {
|
||||
getLogger().log(Level.SEVERE, "Votifier did not initialize properly!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the instance.
|
||||
*
|
||||
* @return The instance
|
||||
*/
|
||||
public static NuVotifierBukkit getInstance() {
|
||||
return instance;
|
||||
@Override
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return pluginLogger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version.
|
||||
*
|
||||
* @return The version
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
@@ -343,33 +357,29 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(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, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,11 +388,30 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
if (debug) {
|
||||
getLogger().info("Got a forwarded vote -> " + v);
|
||||
}
|
||||
Bukkit.getScheduler().runTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bukkit.getPluginManager().callEvent(new VotifierEvent(v));
|
||||
}
|
||||
});
|
||||
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,36 @@
|
||||
package com.vexsoftware.votifier.cmd;
|
||||
|
||||
import com.vexsoftware.votifier.NuVotifierBukkit;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
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(Component.text("Reloading NuVotifier...").color(NamedTextColor.GRAY));
|
||||
if (plugin.reload()) {
|
||||
sender.sendMessage(
|
||||
Component.text("NuVotifier has been reloaded!").color(NamedTextColor.DARK_GREEN));
|
||||
} else {
|
||||
sender.sendMessage(
|
||||
Component.text("Looks like there was a problem reloading NuVotifier, check the console!")
|
||||
.color(NamedTextColor.DARK_RED));
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(
|
||||
Component.text("You do not have permission to do this!").color(NamedTextColor.DARK_RED));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
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 net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
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(
|
||||
Component.text("Error while parsing arguments to create test vote: " + e.getMessage())
|
||||
.color(NamedTextColor.DARK_RED));
|
||||
sender.sendMessage(Component.text(
|
||||
"Usage hint: /testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]")
|
||||
.color(NamedTextColor.GRAY));
|
||||
return true;
|
||||
}
|
||||
|
||||
plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
|
||||
sender.sendMessage(
|
||||
Component.text("Test vote executed: " + v.toString()).color(NamedTextColor.GREEN));
|
||||
} else {
|
||||
sender.sendMessage(
|
||||
Component.text("You do not have permission to do this!").color(NamedTextColor.DARK_RED));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+10
-20
@@ -1,34 +1,27 @@
|
||||
package com.vexsoftware.votifier.forwarding;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.NuVotifierBukkit;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import org.apache.commons.lang.Validate;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSink;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class BukkitPluginMessagingForwardingSink implements ForwardingVoteSink, PluginMessageListener {
|
||||
public class BukkitPluginMessagingForwardingSink extends AbstractPluginMessagingForwardingSink
|
||||
implements PluginMessageListener {
|
||||
|
||||
public BukkitPluginMessagingForwardingSink(Plugin p, String channel, ForwardedVoteListener listener) {
|
||||
Validate.notNull(channel, "Channel cannot be null.");
|
||||
super(listener);
|
||||
Objects.requireNonNull(channel, "Channel cannot be null."); // Replaced Validate.notNull
|
||||
this.channel = channel;
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(p, channel, this);
|
||||
this.listener = listener;
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
private final Plugin p;
|
||||
private final String channel;
|
||||
private final ForwardedVoteListener listener;
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
@@ -38,12 +31,9 @@ public class BukkitPluginMessagingForwardingSink implements ForwardingVoteSink,
|
||||
@Override
|
||||
public void onPluginMessageReceived(String s, Player player, byte[] bytes) {
|
||||
try {
|
||||
String message = new String(bytes, StandardCharsets.UTF_8);
|
||||
JsonObject jsonObject = GsonInst.gson.fromJson(message, JsonObject.class);
|
||||
Vote v = new Vote(jsonObject);
|
||||
listener.onForward(v);
|
||||
this.handlePluginMessage(bytes);
|
||||
} catch (Exception e) {
|
||||
NuVotifierBukkit.getInstance().getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
|
||||
p.getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.vexsoftware.votifier.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
/**
|
||||
* Represents a method to call when a vote is forwarded through the {@link ForwardingVoteSink}.
|
||||
*/
|
||||
public interface ForwardedVoteListener {
|
||||
|
||||
/**
|
||||
* Called whenever a vote is forwarded from a {@link ForwardingVoteSink}
|
||||
*
|
||||
* @param v Vote that was forwarded
|
||||
*/
|
||||
void onForward(Vote v);
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
|
||||
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
|
||||
host: %ip%
|
||||
host: 0.0.0.0
|
||||
|
||||
# Port to listen for new votes on
|
||||
port: 8192
|
||||
|
||||
# Whether or not to print debug messages. In a production system, this should be set to false.
|
||||
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
|
||||
debug: false
|
||||
|
||||
# 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.
|
||||
@@ -17,7 +13,7 @@ disable-v1-protocol: false
|
||||
# All tokens, labeled by the serviceName of each server list.
|
||||
tokens:
|
||||
# Default token for all server lists, if another isn't supplied.
|
||||
default: %default_token%
|
||||
default: "^{VOTIFIER_OG_TOKEN}"
|
||||
|
||||
# Configuration section for all vote forwarding to NuVotifier
|
||||
forwarding:
|
||||
@@ -26,4 +22,4 @@ forwarding:
|
||||
# - pluginMessaging - Sets up plugin messaging
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
channel: nuvotifier:votes
|
||||
@@ -1,5 +1,26 @@
|
||||
name: Votifier
|
||||
name: Votifier-OG
|
||||
main: com.vexsoftware.votifier.NuVotifierBukkit
|
||||
version: @app.version@
|
||||
version: "${version}"
|
||||
description: A plugin that gets notified when votes are made for the server on toplists.
|
||||
authors: [ParallelBlock, blakeman8192, Kramer, tuxed]
|
||||
authors: [Ichbinjoe, blakeman8192, Kramer, tuxed, NotAlexNoyle]
|
||||
api-version: 1.19
|
||||
|
||||
commands:
|
||||
nvreload:
|
||||
description: Reloads the NuVotifier configuration
|
||||
permission: nuvotifier.reload
|
||||
permission-message: "You do not have permission to run this command."
|
||||
usage: "/nvreload"
|
||||
testvote:
|
||||
description: Sends a test vote to the server
|
||||
permission: nuvotifier.testvote
|
||||
permission-message: "You do not have permission to run this command."
|
||||
usage: "/testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]"
|
||||
|
||||
permissions:
|
||||
nuvotifier.reload:
|
||||
description: Allows you to reload the NuVotifier plugin
|
||||
default: op
|
||||
nuvotifier.testvote:
|
||||
description: Allows you to send a test vote
|
||||
default: op
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly group: "net.md-5", name: "bungeecord-api", version: "1.8-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
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.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.bungee.events.VotifierEvent;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.OnlineForwardPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.PluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.MemoryVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
*/
|
||||
private KeyPair keyPair;
|
||||
|
||||
/**
|
||||
* Debug mode flag
|
||||
*/
|
||||
private boolean debug;
|
||||
|
||||
/**
|
||||
* Keys used for websites.
|
||||
*/
|
||||
private Map<String, Key> tokens = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Method used to forward votes to downstream servers
|
||||
*/
|
||||
private ForwardingVoteSource forwardingMethod;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
// Handle configuration.
|
||||
File config = new File(getDataFolder() + "/config.yml");
|
||||
File rsaDirectory = new File(getDataFolder() + "/rsa");
|
||||
Configuration configuration;
|
||||
|
||||
if (!config.exists()) {
|
||||
try {
|
||||
// First time run - do some initialization.
|
||||
getLogger().info("Configuring Votifier for the first time...");
|
||||
|
||||
// Initialize the configuration file.
|
||||
config.createNewFile();
|
||||
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token);
|
||||
Files.write(cfgStr, config, StandardCharsets.UTF_8);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
* port number.
|
||||
*/
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Assigning NuVotifier to listen on port 8192. If you are hosting BungeeCord on a");
|
||||
getLogger().info("shared server please check with your hosting provider to verify that this port");
|
||||
getLogger().info("is available for your use. Chances are that your hosting provider will assign");
|
||||
getLogger().info("a different port, which you need to specify in config.yml");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Assigning NuVotifier to listen to interface 0.0.0.0. This is usually alright,");
|
||||
getLogger().info("however, if you want NuVotifier to only listen to one interface for security ");
|
||||
getLogger().info("reasons (or you use a shared host), you may change this in the config.yml.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Your default Votifier token is " + token + ".");
|
||||
getLogger().info("You will need to provide this token when you submit your server to a voting");
|
||||
getLogger().info("list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Unable to create configuration file", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the configuration.
|
||||
try {
|
||||
configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(config);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to load configuration", e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create RSA directory and keys if it does not exist; otherwise, read
|
||||
* keys.
|
||||
*/
|
||||
try {
|
||||
if (!rsaDirectory.exists()) {
|
||||
rsaDirectory.mkdir();
|
||||
keyPair = RSAKeygen.generate(2048);
|
||||
RSAIO.save(rsaDirectory, keyPair);
|
||||
} else {
|
||||
keyPair = RSAIO.load(rsaDirectory);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Error reading RSA tokens", ex);
|
||||
}
|
||||
|
||||
// Load Votifier tokens.
|
||||
Configuration tokenSection = configuration.getSection("tokens");
|
||||
|
||||
if (configuration.get("tokens") != null) {
|
||||
for (String s : tokenSection.getKeys()) {
|
||||
tokens.put(s, KeyCreator.createKeyFrom(tokenSection.getString(s)));
|
||||
getLogger().info("Loaded token for website: " + s);
|
||||
}
|
||||
} else {
|
||||
String token = TokenUtil.newToken();
|
||||
configuration.set("tokens", ImmutableMap.of("default", token));
|
||||
tokens.put("default", KeyCreator.createKeyFrom(token));
|
||||
try {
|
||||
ConfigurationProvider.getProvider(YamlConfiguration.class).save(configuration, config);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error generating Votifier token", e);
|
||||
}
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
|
||||
getLogger().info("Your default Votifier token is " + token + ".");
|
||||
getLogger().info("You will need to provide this token when you submit your server to a voting");
|
||||
getLogger().info("list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
// Initialize the receiver.
|
||||
final String host = configuration.getString("host", "0.0.0.0");
|
||||
final int port = configuration.getInt("port", 8192);
|
||||
debug = configuration.getBoolean("debug", false);
|
||||
if (debug)
|
||||
getLogger().info("DEBUG mode enabled!");
|
||||
|
||||
final boolean disablev1 = configuration.getBoolean("disable-v1-protocol");
|
||||
if (disablev1) {
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
|
||||
getLogger().info("currently support the modern Votifier protocol in NuVotifier.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
// Must set up server asynchronously due to BungeeCord goofiness.
|
||||
FutureTask<?> initTask = new FutureTask<>(Executors.callable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
serverGroup = new NioEventLoopGroup(2);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(NuVotifier.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(NuVotifier.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
getProxy().getScheduler().runAsync(this, initTask);
|
||||
try {
|
||||
initTask.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException("Unable to start server", e);
|
||||
}
|
||||
|
||||
Configuration fwdCfg = configuration.getSection("forwarding");
|
||||
String fwdMethod = fwdCfg.getString("method", "none").toLowerCase();
|
||||
if ("none".equals(fwdMethod)) {
|
||||
getLogger().info("Method none selected for vote forwarding: Votes will not be forwarded to backend servers.");
|
||||
} else if ("pluginmessaging".equals(fwdMethod)) {
|
||||
String channel = fwdCfg.getString("pluginMessaging.channel", "NuVotifier");
|
||||
String cacheMethod = fwdCfg.getString("pluginMessaging.cache", "file").toLowerCase();
|
||||
VoteCache voteCache = null;
|
||||
if ("none".equals(cacheMethod)) {
|
||||
getLogger().info("Vote cache none selected for caching: votes that cannot be immediately delivered will be lost.");
|
||||
} else if ("memory".equals(cacheMethod)) {
|
||||
voteCache = new MemoryVoteCache(ProxyServer.getInstance().getServers().size());
|
||||
getLogger().info("Using in-memory cache for votes that are not able to be delivered.");
|
||||
} else if ("file".equals(cacheMethod)) {
|
||||
try {
|
||||
voteCache = new FileVoteCache(
|
||||
ProxyServer.getInstance().getServers().size(), this,
|
||||
new File(getDataFolder(), fwdCfg.getString("pluginMessaging.file.name")),
|
||||
fwdCfg.getInt("pluginMessaging.file.cacheTime", -1));
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE, "Unload to load file cache. Votes will be lost!", e);
|
||||
}
|
||||
}
|
||||
if (!fwdCfg.getBoolean("pluginMessaging.onlySendToJoinedServer")) {
|
||||
|
||||
List<String> ignoredServers = fwdCfg.getStringList("pluginMessaging.excludedServers");
|
||||
|
||||
try {
|
||||
forwardingMethod = new PluginMessagingForwardingSource(channel, ignoredServers, this, voteCache);
|
||||
getLogger().info("Forwarding votes over PluginMessaging channel '" + channel + "' for vote forwarding!");
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
String fallbackServer = fwdCfg.getString("pluginMessaging.joinedServerFallback", null);
|
||||
if (fallbackServer != null && fallbackServer.isEmpty()) fallbackServer = null;
|
||||
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, voteCache, fallbackServer);
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
}
|
||||
} else if ("proxy".equals(fwdMethod)) {
|
||||
Configuration serverSection = fwdCfg.getSection("proxy");
|
||||
List<ProxyForwardingVoteSource.BackendServer> serverList = new ArrayList<>();
|
||||
for (String s : serverSection.getKeys()) {
|
||||
Configuration section = serverSection.getSection(s);
|
||||
InetAddress address;
|
||||
try {
|
||||
address = InetAddress.getByName(section.getString("address"));
|
||||
} catch (UnknownHostException e) {
|
||||
getLogger().info("Address " + section.getString("address") + " couldn't be looked up. Ignoring!");
|
||||
continue;
|
||||
}
|
||||
|
||||
Key token = null;
|
||||
try {
|
||||
token = KeyCreator.createKeyFrom(section.getString("token", section.getString("key")));
|
||||
} catch (IllegalArgumentException e) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"An exception occurred while attempting to add proxy target '" + s + "' - maybe your token is wrong? " +
|
||||
"Votes will not be forwarded to this server!", e);
|
||||
}
|
||||
|
||||
if (token != null) {
|
||||
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
|
||||
new InetSocketAddress(address, section.getInt("port")),
|
||||
token);
|
||||
serverList.add(server);
|
||||
}
|
||||
}
|
||||
|
||||
forwardingMethod = new ProxyForwardingVoteSource(this, serverGroup, serverList, null);
|
||||
getLogger().info("Forwarding votes from this NuVotifier instance to another NuVotifier server.");
|
||||
} else {
|
||||
getLogger().severe("No vote forwarding method '" + fwdMethod + "' known. Defaulting to noop implementation.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Shut down the network handlers.
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
}
|
||||
|
||||
getLogger().info("Votifier disabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getProxy().getPluginManager().callEvent(new VotifierEvent(vote));
|
||||
}
|
||||
});
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
forwardingMethod.forward(vote);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
if (debug) {
|
||||
if (alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Key> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair getProtocolV1Key() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return getDescription().getVersion();
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.events;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
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 {
|
||||
/**
|
||||
* The vote.
|
||||
**/
|
||||
private final Vote vote;
|
||||
|
||||
public VotifierEvent(Vote vote) {
|
||||
this.vote = vote;
|
||||
}
|
||||
|
||||
public Vote getVote() {
|
||||
return vote;
|
||||
}
|
||||
}
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSource implements ForwardingVoteSource {
|
||||
|
||||
public AbstractPluginMessagingForwardingSource(String channel, List<String> ignoredServers, NuVotifier nuVotifier, VoteCache cache) {
|
||||
ProxyServer.getInstance().registerChannel(channel);
|
||||
this.channel = channel;
|
||||
this.nuVotifier = nuVotifier;
|
||||
this.cache = cache;
|
||||
this.ignoredServers = ignoredServers;
|
||||
}
|
||||
|
||||
protected AbstractPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache) {
|
||||
this(channel, null, nuVotifier, voteCache);
|
||||
}
|
||||
|
||||
protected final NuVotifier nuVotifier;
|
||||
protected final String channel;
|
||||
protected final VoteCache cache;
|
||||
protected final List<String> ignoredServers;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
for (ServerInfo s : ProxyServer.getInstance().getServers().values()) {
|
||||
if (ignoredServers != null && ignoredServers.contains(s.getName())) continue;
|
||||
if (!forwardSpecific(s, rawData)) attemptToAddToCache(v, s.getName());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean forwardSpecific(ServerInfo connection, Vote vote) {
|
||||
byte[] rawData = vote.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
return connection.sendData(channel, rawData, false);
|
||||
}
|
||||
|
||||
private boolean forwardSpecific(ServerInfo connection, byte[] data) {
|
||||
return connection.sendData(channel, data, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
ProxyServer.getInstance().unregisterChannel(channel);
|
||||
|
||||
if (cache != null && cache instanceof FileVoteCache) {
|
||||
try {
|
||||
((FileVoteCache) cache).halt();
|
||||
} catch (IOException e) {
|
||||
nuVotifier.getLogger().log(Level.SEVERE, "Unable to save cached votes, votes will be lost.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleServerConnected(final ServerConnectedEvent e) {
|
||||
if (cache == null) return;
|
||||
final String serverName = e.getServer().getInfo().getName();
|
||||
final Collection<Vote> cachedVotes = cache.evict(serverName);
|
||||
final Collection<Vote> failedVotes = dumpVotesToServer(cachedVotes, e.getServer().getInfo(), "server '" + serverName + "'");
|
||||
for (Vote v : failedVotes)
|
||||
cache.addToCache(v, serverName);
|
||||
}
|
||||
|
||||
protected void attemptToAddToCache(Vote v, String server) {
|
||||
if (cache != null) {
|
||||
cache.addToCache(v, server);
|
||||
if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().info("Added to forwarding cache: " + v + " -> " + server);
|
||||
} else if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().severe("Could not immediately send vote to backend, vote lost! " + v + " -> " + server);
|
||||
}
|
||||
|
||||
protected void attempToAddToPlayerCache(Vote v, String player) {
|
||||
if (cache != null) {
|
||||
cache.addToCachePlayer(v, player);
|
||||
if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().info("Added to forwarding cache: " + v + " -> (player) " + player);
|
||||
} else if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().severe("Could not immediately send vote to backend, vote lost! " + v + " -> (player) " + player);
|
||||
|
||||
}
|
||||
|
||||
// returns a collection of failed votes
|
||||
private Collection<Vote> dumpVotesToServer(Collection<Vote> cachedVotes, ServerInfo target, String identifier) {
|
||||
List<Vote> failures = new ArrayList<>();
|
||||
|
||||
if (!cachedVotes.isEmpty()) {
|
||||
nuVotifier.getProxy().getScheduler().schedule(nuVotifier, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int evicted = 0;
|
||||
int unsuccessfulEvictions = 0;
|
||||
for (Vote v : cachedVotes) {
|
||||
if (forwardSpecific(target, v)) {
|
||||
evicted++;
|
||||
} else {
|
||||
// Re-add to cache to send later.
|
||||
failures.add(v);
|
||||
unsuccessfulEvictions++;
|
||||
}
|
||||
}
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Successfully evicted " + evicted + " votes to " + identifier + ".");
|
||||
if (unsuccessfulEvictions > 0) {
|
||||
nuVotifier.getLogger().info("Held " + unsuccessfulEvictions + " votes for " + identifier + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
protected void handlePlayerSwitch(ServerConnectedEvent e) {
|
||||
if (cache == null) return;
|
||||
if (ignoredServers != null && ignoredServers.contains(e.getServer().getInfo().getName())) return;
|
||||
|
||||
String playerName = e.getPlayer().getName();
|
||||
|
||||
final Collection<Vote> cachedVotes = cache.evictPlayer(playerName);
|
||||
final Collection<Vote> failedVotes = dumpVotesToServer(cachedVotes, e.getServer().getInfo(), "player '" + playerName);
|
||||
for (Vote v : failedVotes)
|
||||
cache.addToCachePlayer(v, playerName);
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
/**
|
||||
* @author Joseph Hirschfeld
|
||||
* @date 12/31/2015
|
||||
*/
|
||||
public final class OnlineForwardPluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
|
||||
public OnlineForwardPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache cache, String fallbackServer) {
|
||||
super(channel, nuVotifier, cache);
|
||||
this.fallbackServer = fallbackServer;
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
private final String fallbackServer;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
ProxiedPlayer p = ProxyServer.getInstance().getPlayer(v.getUsername());
|
||||
if (p == null || !forwardSpecific(p.getServer().getInfo(), v)) {
|
||||
ServerInfo serverInfo = ProxyServer.getInstance().getServers().get(fallbackServer);
|
||||
|
||||
// nowhere to fall back to, yet still not online. lets save this vote yet!
|
||||
if (serverInfo == null)
|
||||
attempToAddToPlayerCache(v, v.getUsername());
|
||||
|
||||
else if (!forwardSpecific(serverInfo, v))
|
||||
attemptToAddToCache(v, fallbackServer);
|
||||
}
|
||||
}
|
||||
|
||||
// The below is to allow us to share code since the event handlers from the parent class don't get
|
||||
// picked up.
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getTag().equals(channel)) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
handleServerConnected(e);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerSwitch(ServerConnectedEvent e) {
|
||||
handlePlayerSwitch(e);
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
|
||||
|
||||
public PluginMessagingForwardingSource(String channel, List<String> ignoredServers, NuVotifier nuVotifier, VoteCache cache) {
|
||||
super(channel, ignoredServers, nuVotifier, cache);
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
protected PluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache) {
|
||||
super(channel, nuVotifier, voteCache);
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getTag().equals(channel)) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
handleServerConnected(e);
|
||||
}
|
||||
}
|
||||
Vendored
-167
@@ -1,167 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class FileVoteCache extends MemoryVoteCache {
|
||||
|
||||
private final Logger l;
|
||||
private final File cacheFile;
|
||||
private final int voteTTL;
|
||||
private final ScheduledTask saveTask;
|
||||
|
||||
public FileVoteCache(int initialMemorySize, final Plugin plugin, File cacheFile, int voteTTL) throws IOException {
|
||||
super(initialMemorySize);
|
||||
this.cacheFile = cacheFile;
|
||||
this.voteTTL = voteTTL;
|
||||
this.l = plugin.getLogger();
|
||||
|
||||
load();
|
||||
|
||||
saveTask = ProxyServer.getInstance().getScheduler().schedule(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
save();
|
||||
} catch (IOException e) {
|
||||
l.log(Level.SEVERE, "Unable to save cached votes, votes will be lost if you restart.", e);
|
||||
}
|
||||
}
|
||||
}, 3, 3, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void load() throws IOException {
|
||||
// Load the cache from disk
|
||||
JsonObject object;
|
||||
try (BufferedReader reader = Files.newReader(cacheFile, StandardCharsets.UTF_8)) {
|
||||
object = GsonInst.gson.fromJson(reader, JsonObject.class);
|
||||
} catch (FileNotFoundException e) {
|
||||
object = new JsonObject();
|
||||
}
|
||||
|
||||
boolean resave = false;
|
||||
|
||||
// First, lets figure out if we are converting from a pre2.3.6 cache
|
||||
if (!object.has("players") || !object.has("servers") || !object.has("version") || object.size() != 3) {
|
||||
JsonObject oldObject = object;
|
||||
object = new JsonObject();
|
||||
object.add("servers", oldObject);
|
||||
object.add("players", new JsonObject());
|
||||
object.addProperty("version", 2);
|
||||
resave = true;
|
||||
}
|
||||
|
||||
if (object.get("version").getAsInt() != 2)
|
||||
throw new IllegalStateException("Could not read cache file! Unknown version '" + object.get("version").getAsInt() + "' read.");
|
||||
|
||||
JsonObject players = object.getAsJsonObject("players");
|
||||
JsonObject servers = object.getAsJsonObject("servers");
|
||||
|
||||
for (String player : players.keySet()) {
|
||||
playerVoteCache.put(player, readVotes(players.getAsJsonArray(player)));
|
||||
}
|
||||
|
||||
for (String server : servers.keySet()) {
|
||||
voteCache.put(server, readVotes(servers.getAsJsonArray(server)));
|
||||
}
|
||||
|
||||
if (resave) {
|
||||
File replacementFile;
|
||||
for (int i = 0; ; i++) {
|
||||
replacementFile = new File(cacheFile.getParentFile(), cacheFile.getName() + ".bak." + i);
|
||||
if (!replacementFile.exists())
|
||||
break;
|
||||
}
|
||||
|
||||
if (!cacheFile.renameTo(replacementFile)) {
|
||||
l.log(Level.SEVERE, "Backup movement failed! Will not save.");
|
||||
return;
|
||||
}
|
||||
|
||||
l.log(Level.WARNING, "Saving new vote cache format to file - backup moved to " + replacementFile.getAbsolutePath());
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Vote> readVotes(JsonArray voteArray) {
|
||||
List<Vote> votes = new ArrayList<>(voteArray.size());
|
||||
for (int i = 0; i < voteArray.size(); i++) {
|
||||
JsonObject voteObject = voteArray.get(i).getAsJsonObject();
|
||||
Vote v = new Vote(voteObject);
|
||||
if (hasTimedOut(v))
|
||||
l.log(Level.WARNING, "Purging out of date vote.", v);
|
||||
else
|
||||
votes.add(v);
|
||||
}
|
||||
return votes;
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
cacheLock.lock();
|
||||
JsonObject votesObject = new JsonObject();
|
||||
votesObject.addProperty("version", 2);
|
||||
try {
|
||||
votesObject.add("players", serializeMap(playerVoteCache));
|
||||
votesObject.add("servers", serializeMap(voteCache));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) {
|
||||
GsonInst.gson.toJson(votesObject, writer);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonObject serializeMap(Map<String, Collection<Vote>> map) {
|
||||
JsonObject o = new JsonObject();
|
||||
|
||||
Iterator<Map.Entry<String, Collection<Vote>>> entryItr = map.entrySet().iterator();
|
||||
while (entryItr.hasNext()) {
|
||||
Map.Entry<String, Collection<Vote>> entry = entryItr.next();
|
||||
JsonArray array = new JsonArray();
|
||||
Iterator<Vote> voteItr = entry.getValue().iterator();
|
||||
while (voteItr.hasNext()) {
|
||||
Vote vote = voteItr.next();
|
||||
|
||||
// if the vote is no longer valid, notify and remove
|
||||
if (hasTimedOut(vote)) {
|
||||
l.log(Level.WARNING, "Purging out of date vote.", vote);
|
||||
voteItr.remove();
|
||||
} else {
|
||||
array.add(vote.serialize());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if, during our iteration, we TTL invalidated all of the votes
|
||||
if (entry.getValue().isEmpty())
|
||||
entryItr.remove();
|
||||
o.add(entry.getKey(), array);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
private boolean hasTimedOut(Vote v) {
|
||||
if (voteTTL == -1) return false;
|
||||
// scale voteTTL to milliseconds
|
||||
return v.getLocalTimestamp() + voteTTL * 24 * 60 * 60 * 1000 < System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void halt() throws IOException {
|
||||
saveTask.cancel();
|
||||
save();
|
||||
}
|
||||
}
|
||||
Vendored
-90
@@ -1,90 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class MemoryVoteCache implements VoteCache {
|
||||
|
||||
public MemoryVoteCache(int initialSize) {
|
||||
voteCache = new HashMap<>(initialSize);
|
||||
this.playerVoteCache = new HashMap<>();
|
||||
}
|
||||
|
||||
protected final Map<String, Collection<Vote>> voteCache;
|
||||
|
||||
protected final Map<String, Collection<Vote>> playerVoteCache;
|
||||
|
||||
protected final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
@Override
|
||||
public Collection<String> getCachedServers() {
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return ImmutableList.copyOf(voteCache.keySet());
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCache(Vote v, String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
Collection<Vote> voteCollection = voteCache.get(server);
|
||||
if (voteCollection == null) {
|
||||
voteCollection = new ArrayList<>();
|
||||
voteCache.put(server, voteCollection);
|
||||
}
|
||||
voteCollection.add(v);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCachePlayer(Vote v, String player) {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
Collection<Vote> voteCollection = playerVoteCache.get(player);
|
||||
if (voteCollection == null) {
|
||||
voteCollection = new ArrayList<>();
|
||||
playerVoteCache.put(player, voteCollection);
|
||||
}
|
||||
voteCollection.add(v);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evictPlayer(String player) {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
Collection<Vote> fromCollection = playerVoteCache.remove(player);
|
||||
return fromCollection != null ? fromCollection : ImmutableList.of();
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evict(String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
Collection<Vote> fromCollection = voteCache.remove(server);
|
||||
return fromCollection != null ? fromCollection : ImmutableList.of();
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
-140
@@ -1,140 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierProtocol2Encoder;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierResponseHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.Delimiters;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
private static final int MAX_RETRIES = 5;
|
||||
private final NuVotifier plugin;
|
||||
private final NioEventLoopGroup eventLoopGroup;
|
||||
private final List<BackendServer> backendServers;
|
||||
private final VoteCache voteCache;
|
||||
|
||||
private static final StringDecoder STRING_DECODER = new StringDecoder(StandardCharsets.US_ASCII);
|
||||
|
||||
public ProxyForwardingVoteSource(NuVotifier plugin, NioEventLoopGroup eventLoopGroup, List<BackendServer> backendServers, VoteCache voteCache) {
|
||||
this.plugin = plugin;
|
||||
this.eventLoopGroup = eventLoopGroup;
|
||||
this.backendServers = backendServers;
|
||||
this.voteCache = voteCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
for (final BackendServer server : backendServers) {
|
||||
forwardVote(server, v, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private int fib(int t) {
|
||||
if (t <= 1) return 1;
|
||||
return fib(t - 2) + fib(t - 1);
|
||||
}
|
||||
|
||||
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
|
||||
new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(eventLoopGroup)
|
||||
.handler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(8, TimeUnit.SECONDS));
|
||||
channel.pipeline().addLast(STRING_DECODER);
|
||||
channel.pipeline().addLast(new VotifierProtocol2Encoder(server.key));
|
||||
channel.pipeline().addLast(new VotifierProtocol2HandshakeHandler(v, new VotifierResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable error) {
|
||||
handleFailure(server, v, error, tries);
|
||||
}
|
||||
}, plugin));
|
||||
}
|
||||
})
|
||||
.connect(server.address)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleFailure(final BackendServer server, final Vote v, Throwable cause, final int tries) {
|
||||
int nextDelay = fib(tries + 1);
|
||||
boolean willRetry = tries < MAX_RETRIES;
|
||||
|
||||
String msg = "Unable to send vote to " + server.address + ".";
|
||||
if (willRetry) {
|
||||
msg += " Will retry sending in " + nextDelay + " second(s).";
|
||||
} else {
|
||||
if (voteCache == null) {
|
||||
msg += " This vote will be lost!";
|
||||
} else {
|
||||
voteCache.addToCache(v, server.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getLogger().log(Level.SEVERE, msg, cause);
|
||||
} else {
|
||||
plugin.getLogger().log(Level.SEVERE, msg);
|
||||
}
|
||||
|
||||
if (willRetry) {
|
||||
plugin.getProxy().getScheduler().schedule(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
forwardVote(server, v, tries + 1);
|
||||
}
|
||||
}, nextDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
|
||||
}
|
||||
|
||||
public static class BackendServer {
|
||||
private final String name;
|
||||
private final InetSocketAddress address;
|
||||
private final Key key;
|
||||
|
||||
public BackendServer(String name, InetSocketAddress address, Key key) {
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
name: NuVotifier
|
||||
main: com.vexsoftware.votifier.bungee.NuVotifier
|
||||
version: @app.version@
|
||||
author: ParallelBlock, blakeman8192, Kramer, tuxed
|
||||
@@ -1,67 +0,0 @@
|
||||
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
|
||||
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
|
||||
host: 0.0.0.0
|
||||
|
||||
# Port to listen for new votes on
|
||||
port: 8192
|
||||
|
||||
# Whether or not to print debug messages. In a production system, this should be set to false.
|
||||
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
|
||||
debug: false
|
||||
|
||||
# All tokens, labeled by the serviceName of each server list.
|
||||
tokens:
|
||||
# Default token for all server lists, if another isn't supplied.
|
||||
default: %default_token%
|
||||
|
||||
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
|
||||
# option is currently not recommended as most voting sites only support the old protocol at present.
|
||||
disable-v1-protocol: false
|
||||
|
||||
# Configuration section for all vote forwarding to NuVotifier
|
||||
forwarding:
|
||||
# Sets whether to set up a remote method for fowarding. Supported methods:
|
||||
# - none - Does not set up a forwarding method.
|
||||
# - pluginMessaging - Sets up plugin messaging.
|
||||
# - proxy - Proxies votes to other NuVotifier servers from this server.
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
|
||||
#Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
|
||||
#to them. If excludedServers is not present, votes will be sent to all servers.
|
||||
#If onlySendToJoinedServer is true, this option is ignored.
|
||||
|
||||
#excludedServers:
|
||||
#- server1
|
||||
#- server2
|
||||
|
||||
|
||||
# Sets which cache to use if there are no players on the server that the vote is being sent to. Supported caches:
|
||||
# - none - Votes will simply be lost when no players are present on the server.
|
||||
# - memory - Votes will be cached in memory when no players are present. When Bungee is restarted, the votes will be lost.
|
||||
# This method is NOT recommended for production systems!
|
||||
# - file - Votes will be saved to a local file every 3 minutes and on shutdown and reloaded when Bungee is restarted. This is suitable for
|
||||
# most set ups.
|
||||
cache: file
|
||||
# Only sends the vote to the server in which the player is currently joined.
|
||||
onlySendToJoinedServer: false
|
||||
#Sets which server to send the vote in the event the player is not online when the vote is cast
|
||||
#If you do not want the vote forwarded to a fallback, set this value to empty ('')
|
||||
#ONLY USED IF onlySendToJoinedServer is true!!
|
||||
joinedServerFallback: 'Hub'
|
||||
# Options for file caching.
|
||||
file:
|
||||
name: cached-votes.json
|
||||
# days before a vote is considered 'dead' - removed from cache with a console warning
|
||||
voteTTL: 10
|
||||
# Specify servers to proxy votes for.
|
||||
proxy:
|
||||
Hub:
|
||||
address: 127.0.0.1
|
||||
port: 8193
|
||||
token: MyKeyHere
|
||||
Hub2:
|
||||
address: 127.0.0.1
|
||||
port: 8194
|
||||
token: AnotherKeyHere
|
||||
@@ -0,0 +1 @@
|
||||
/bin/
|
||||
@@ -1,7 +0,0 @@
|
||||
dependencies {
|
||||
compile group: "io.netty", name: "netty-handler", version: "4.1.16.Final"
|
||||
compile 'com.google.code.gson:gson:2.8.2'
|
||||
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
|
||||
}
|
||||
@@ -2,19 +2,13 @@ package com.vexsoftware.votifier;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
public interface VoteHandler {
|
||||
void onVoteReceived(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,17 +0,0 @@
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VotifierPlugin {
|
||||
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
|
||||
|
||||
Map<String, Key> getTokens();
|
||||
|
||||
KeyPair getProtocolV1Key();
|
||||
|
||||
String getVersion();
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.vexsoftware.votifier.net;
|
||||
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.epoll.EpollSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.util.concurrent.FastThreadLocalThread;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class VotifierServerBootstrap {
|
||||
private static final boolean USE_EPOLL = Epoll.isAvailable();
|
||||
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final EventLoopGroup bossLoopGroup;
|
||||
private final EventLoopGroup eventLoopGroup;
|
||||
private final VotifierPlugin plugin;
|
||||
private final boolean v1Disable;
|
||||
|
||||
private Channel serverChannel;
|
||||
|
||||
public VotifierServerBootstrap(String host, int port, VotifierPlugin plugin, boolean v1Disable) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.plugin = plugin;
|
||||
this.v1Disable = v1Disable;
|
||||
if (USE_EPOLL) {
|
||||
this.bossLoopGroup = new EpollEventLoopGroup(1, createThreadFactory("Votifier epoll boss"));
|
||||
this.eventLoopGroup = new EpollEventLoopGroup(3, createThreadFactory("Votifier epoll worker"));
|
||||
plugin.getPluginLogger().info("Using epoll transport to accept votes.");
|
||||
} else {
|
||||
this.bossLoopGroup = new NioEventLoopGroup(1, createThreadFactory("Votifier NIO boss"));
|
||||
this.eventLoopGroup = new NioEventLoopGroup(3, createThreadFactory("Votifier NIO worker"));
|
||||
plugin.getPluginLogger().info("Using NIO transport to accept votes.");
|
||||
}
|
||||
}
|
||||
|
||||
private static ThreadFactory createThreadFactory(String name) {
|
||||
return runnable -> {
|
||||
FastThreadLocalThread thread = new FastThreadLocalThread(runnable, name);
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
};
|
||||
}
|
||||
|
||||
public void start(Consumer<Throwable> error) {
|
||||
Objects.requireNonNull(error, "error");
|
||||
|
||||
VoteInboundHandler voteInboundHandler = new VoteInboundHandler(plugin);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(USE_EPOLL ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
|
||||
.group(bossLoopGroup, eventLoopGroup)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel channel) {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(plugin);
|
||||
channel.pipeline().addLast("greetingHandler", VotifierGreetingHandler.INSTANCE);
|
||||
channel.pipeline()
|
||||
.addLast(
|
||||
"protocolDifferentiator",
|
||||
new VotifierProtocolDifferentiator(false, !v1Disable));
|
||||
channel.pipeline().addLast("voteHandler", voteInboundHandler);
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
plugin.getPluginLogger()
|
||||
.info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
|
||||
error.accept(null);
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
plugin.getPluginLogger()
|
||||
.error("Votifier was not able to bind to " + socketAddress.toString(), future.cause());
|
||||
error.accept(future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Bootstrap client() {
|
||||
return new Bootstrap()
|
||||
.channel(USE_EPOLL ? EpollSocketChannel.class : NioSocketChannel.class)
|
||||
.group(eventLoopGroup);
|
||||
}
|
||||
|
||||
public ProxyForwardingVoteSource createForwardingSource(
|
||||
List<ProxyForwardingVoteSource.BackendServer> backendServers, VoteCache voteCache) {
|
||||
return new ProxyForwardingVoteSource(plugin, this::client, backendServers, voteCache);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (serverChannel != null) {
|
||||
try {
|
||||
serverChannel.close().syncUninterruptibly();
|
||||
} catch (Exception e) {
|
||||
plugin.getPluginLogger().error("Unable to shutdown server channel", e);
|
||||
}
|
||||
}
|
||||
eventLoopGroup.shutdownGracefully();
|
||||
bossLoopGroup.shutdownGracefully();
|
||||
|
||||
try {
|
||||
bossLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
eventLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,7 @@ public class VotifierSession {
|
||||
}
|
||||
|
||||
public void completeVote() {
|
||||
if (hasCompletedVote)
|
||||
throw new IllegalStateException("Protocol completed vote twice!");
|
||||
if (hasCompletedVote) throw new IllegalStateException("Protocol completed vote twice!");
|
||||
|
||||
hasCompletedVote = true;
|
||||
}
|
||||
@@ -40,8 +39,15 @@ 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) {
|
||||
@@ -33,13 +41,13 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
handler.onError(ctx.channel(), session.hasCompletedVote(), cause);
|
||||
String remoteAddr = ctx.channel().remoteAddress().toString();
|
||||
boolean hasCompletedVote = session.hasCompletedVote();
|
||||
|
||||
if (session.getVersion() == VotifierSession.ProtocolVersion.TWO) {
|
||||
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("status", "error");
|
||||
object.addProperty("cause", cause.getClass().getSimpleName());
|
||||
@@ -48,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -1,24 +1,26 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Handles the Votifier greeting.
|
||||
*/
|
||||
@ChannelHandler.Sharable
|
||||
public class VotifierGreetingHandler extends ChannelInboundHandlerAdapter {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
public static final VotifierGreetingHandler INSTANCE = new VotifierGreetingHandler();
|
||||
|
||||
String version = "VOTIFIER " + plugin.getVersion() + " " + session.getChallenge() + "\n";
|
||||
private VotifierGreetingHandler() {}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
String version = "VOTIFIER 2 " + session.getChallenge() + "\n";
|
||||
ByteBuf versionBuf = Unpooled.copiedBuffer(version, StandardCharsets.UTF_8);
|
||||
ctx.writeAndFlush(versionBuf);
|
||||
}
|
||||
|
||||
+32
-10
@@ -1,13 +1,14 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.util.QuietException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@@ -17,32 +18,53 @@ import java.util.List;
|
||||
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
if (buf.readableBytes() < 256) {
|
||||
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 - silently drop the connection.
|
||||
buf.skipBytes(buf.readableBytes());
|
||||
ctx.close();
|
||||
return;
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
+7
-6
@@ -1,21 +1,20 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Decodes protocol 2 JSON votes.
|
||||
@@ -44,7 +43,8 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
if (key == null) {
|
||||
key = plugin.getTokens().get("default");
|
||||
if (key == null) {
|
||||
throw new RuntimeException("Unknown service '" + votePayload.get("serviceName").getAsString() + "'");
|
||||
throw new RuntimeException(
|
||||
"Unknown service '" + votePayload.get("serviceName").getAsString() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
|
||||
private boolean hmacEqual(byte[] sig, byte[] message, Key key) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
private boolean hmacEqual(byte[] sig, byte[] message, Key key)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
// See https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||
// This randomizes the byte order to make timing attacks more difficult.
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
|
||||
+23
-12
@@ -1,14 +1,13 @@
|
||||
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;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@@ -16,6 +15,9 @@ 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;
|
||||
@@ -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) {
|
||||
@@ -47,15 +45,28 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
session.setVersion(VotifierSession.ProtocolVersion.TWO);
|
||||
|
||||
if (!testMode) {
|
||||
ctx.pipeline().addAfter("protocolDifferentiator", "protocol2LengthDecoder", new LengthFieldBasedFrameDecoder(1024, 2, 2, 0, 4));
|
||||
ctx.pipeline().addAfter("protocol2LengthDecoder", "protocol2StringDecoder", new StringDecoder(StandardCharsets.UTF_8));
|
||||
ctx.pipeline().addAfter("protocol2StringDecoder", "protocol2VoteDecoder", new VotifierProtocol2Decoder());
|
||||
ctx.pipeline().addAfter("protocol2VoteDecoder", "protocol2StringEncoder", new StringEncoder(StandardCharsets.UTF_8));
|
||||
ctx.pipeline()
|
||||
.addAfter(
|
||||
"protocolDifferentiator",
|
||||
"protocol2LengthDecoder",
|
||||
new LengthFieldBasedFrameDecoder(1024, 2, 2, 0, 4));
|
||||
ctx.pipeline()
|
||||
.addAfter(
|
||||
"protocol2LengthDecoder",
|
||||
"protocol2StringDecoder",
|
||||
new StringDecoder(StandardCharsets.UTF_8));
|
||||
ctx.pipeline()
|
||||
.addAfter("protocol2StringDecoder", "protocol2VoteDecoder", new VotifierProtocol2Decoder());
|
||||
ctx.pipeline()
|
||||
.addAfter(
|
||||
"protocol2VoteDecoder",
|
||||
"protocol2StringEncoder",
|
||||
new StringEncoder(StandardCharsets.UTF_8));
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
} 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);
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Vex Software LLC
|
||||
* This file is part of Votifier.
|
||||
*
|
||||
*
|
||||
* Votifier is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* Votifier is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.vexsoftware.votifier.net.protocol.v1crypto;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
/**
|
||||
* Static RSA utility methods for encrypting and decrypting blocks of
|
||||
@@ -57,5 +57,4 @@ public class RSA {
|
||||
cipher.init(Cipher.DECRYPT_MODE, key);
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Vex Software LLC
|
||||
* This file is part of Votifier.
|
||||
*
|
||||
*
|
||||
* Votifier is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* Votifier is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.vexsoftware.votifier.net.protocol.v1crypto;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.KeyFactory;
|
||||
@@ -29,6 +29,7 @@ import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Static utility methods for saving and loading RSA key pairs.
|
||||
@@ -47,18 +48,32 @@ public class RSAIO {
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
|
||||
// Store the public and private keys.
|
||||
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(
|
||||
publicKey.getEncoded());
|
||||
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(
|
||||
privateKey.getEncoded());
|
||||
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKey.getEncoded());
|
||||
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
|
||||
try (FileOutputStream publicOut = new FileOutputStream(directory + "/public.key");
|
||||
FileOutputStream privateOut = new FileOutputStream(directory + "/private.key")) {
|
||||
FileOutputStream privateOut = new FileOutputStream(directory + "/private.key")) {
|
||||
|
||||
publicOut.write(Base64.getEncoder().encode(publicSpec.getEncoded()));
|
||||
privateOut.write(Base64.getEncoder().encode(privateSpec.getEncoded()));
|
||||
}
|
||||
}
|
||||
|
||||
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, StandardCharsets.US_ASCII);
|
||||
strContents = strContents.trim();
|
||||
try {
|
||||
return Base64.getDecoder().decode(strContents);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Base64 decoding exception: This is probably due to a corrupted file, "
|
||||
+ "but in case it isn't, here is a b64 representation of what we read: "
|
||||
+ new String(Base64.getEncoder().encode(contents), StandardCharsets.UTF_8),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an RSA key pair from a directory. The directory must have the files
|
||||
* "public.key" and "private.key".
|
||||
@@ -69,24 +84,17 @@ public class RSAIO {
|
||||
*/
|
||||
public static KeyPair load(File directory) throws Exception {
|
||||
// Read the public key file.
|
||||
File publicKeyFile = new File(directory + "/public.key");
|
||||
byte[] encodedPublicKey = Files.readAllBytes(publicKeyFile.toPath());
|
||||
encodedPublicKey = Base64.getDecoder().decode(new String(encodedPublicKey, StandardCharsets.UTF_8));
|
||||
byte[] encodedPublicKey = readB64File(directory, "public.key");
|
||||
|
||||
// Read the private key file.
|
||||
File privateKeyFile = new File(directory + "/private.key");
|
||||
byte[] encodedPrivateKey = Files.readAllBytes(privateKeyFile.toPath());
|
||||
encodedPrivateKey = Base64.getDecoder().decode(new String(encodedPrivateKey, StandardCharsets.UTF_8));
|
||||
byte[] encodedPrivateKey = readB64File(directory, "private.key");
|
||||
|
||||
// Instantiate and return the key pair.
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(
|
||||
encodedPublicKey);
|
||||
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey);
|
||||
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
|
||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
|
||||
encodedPrivateKey);
|
||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Vex Software LLC
|
||||
* This file is part of Votifier.
|
||||
*
|
||||
*
|
||||
* Votifier is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* Votifier is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -36,10 +36,8 @@ public class RSAKeygen {
|
||||
*/
|
||||
public static KeyPair generate(int bits) throws Exception {
|
||||
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
|
||||
RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(bits,
|
||||
RSAKeyGenParameterSpec.F4);
|
||||
RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(bits, RSAKeyGenParameterSpec.F4);
|
||||
keygen.initialize(spec);
|
||||
return keygen.generateKeyPair();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
public interface BackendServer {
|
||||
String getName();
|
||||
|
||||
boolean sendPluginMessage(String channel, byte[] data);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class JavaUtilLogger implements LoggingAdapter {
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
public JavaUtilLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s) {
|
||||
logger.severe(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s, Object... o) {
|
||||
logger.log(Level.SEVERE, s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s, Object... o) {
|
||||
logger.log(Level.WARNING, s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s) {
|
||||
logger.info(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s, Object... o) {
|
||||
logger.log(Level.INFO, s, o);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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);
|
||||
|
||||
void info(String s);
|
||||
|
||||
void info(String s, Object... o);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ProxyVotifierPlugin extends VotifierPlugin {
|
||||
Collection<BackendServer> getAllBackendServers();
|
||||
|
||||
Optional<BackendServer> getServer(String name);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.netty.util.AttributeKey;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VotifierPlugin extends VoteHandler {
|
||||
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
|
||||
|
||||
Map<String, Key> getTokens();
|
||||
|
||||
KeyPair getProtocolV1Key();
|
||||
|
||||
LoggingAdapter getPluginLogger();
|
||||
|
||||
VotifierScheduler getScheduler();
|
||||
|
||||
default boolean isDebug() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ScheduledExecutorServiceVotifierScheduler implements VotifierScheduler {
|
||||
private final ScheduledExecutorService service;
|
||||
|
||||
public ScheduledExecutorServiceVotifierScheduler(ScheduledExecutorService service) {
|
||||
this.service = Objects.requireNonNull(service, "service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.schedule(runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.scheduleAtFixedRate(runnable, delay, repeat, unit));
|
||||
}
|
||||
|
||||
private static class ScheduledVotifierTaskWrapper implements ScheduledVotifierTask {
|
||||
private final Future<?> future;
|
||||
|
||||
private ScheduledVotifierTaskWrapper(Future<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
future.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
public interface ScheduledVotifierTask {
|
||||
void cancel();
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface VotifierScheduler {
|
||||
|
||||
ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit);
|
||||
|
||||
ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit);
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import java.io.CharArrayReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSink implements ForwardingVoteSink {
|
||||
|
||||
public AbstractPluginMessagingForwardingSink(ForwardedVoteListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private final ForwardedVoteListener listener;
|
||||
|
||||
public void handlePluginMessage(byte[] message) {
|
||||
String strMessage = new String(message, StandardCharsets.UTF_8);
|
||||
try (CharArrayReader reader = new CharArrayReader(strMessage.toCharArray())) {
|
||||
JsonReader r = new JsonReader(reader);
|
||||
r.setLenient(true);
|
||||
while (r.peek() != JsonToken.END_DOCUMENT) {
|
||||
r.beginObject();
|
||||
JsonObject o = new JsonObject();
|
||||
|
||||
while (r.hasNext()) {
|
||||
String name = r.nextName();
|
||||
if (r.peek() == JsonToken.NUMBER) o.add(name, new JsonPrimitive(r.nextLong()));
|
||||
else o.add(name, new JsonPrimitive(r.nextString()));
|
||||
}
|
||||
r.endObject();
|
||||
|
||||
Vote v = new Vote(o);
|
||||
listener.onForward(v);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(); // Should never happen.
|
||||
}
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSource implements ForwardingVoteSource {
|
||||
|
||||
public AbstractPluginMessagingForwardingSource(
|
||||
String channel, ServerFilter serverFilter, ProxyVotifierPlugin plugin, VoteCache cache, int dumpRate) {
|
||||
this.channel = channel;
|
||||
this.plugin = plugin;
|
||||
this.cache = cache;
|
||||
this.serverFilter = serverFilter;
|
||||
this.dumpRate = dumpRate;
|
||||
}
|
||||
|
||||
protected AbstractPluginMessagingForwardingSource(
|
||||
String channel, ProxyVotifierPlugin plugin, VoteCache voteCache, int dumpRate) {
|
||||
this(channel, null, plugin, voteCache, dumpRate);
|
||||
}
|
||||
|
||||
protected final ProxyVotifierPlugin plugin;
|
||||
protected final String channel;
|
||||
protected final VoteCache cache;
|
||||
protected final ServerFilter serverFilter;
|
||||
private final int dumpRate;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
for (BackendServer server : plugin.getAllBackendServers()) {
|
||||
if (!serverFilter.isAllowed(server.getName())) continue;
|
||||
if (!forwardSpecific(server, rawData)) {
|
||||
attemptToAddToCache(v, server.getName());
|
||||
} else if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to server " + server.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean forwardSpecific(BackendServer connection, Vote vote) {
|
||||
byte[] rawData = vote.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
return forwardSpecific(connection, rawData);
|
||||
}
|
||||
|
||||
protected boolean forwardSpecific(BackendServer connection, Collection<Vote> votes) {
|
||||
StringBuilder data = new StringBuilder();
|
||||
for (Vote v : votes) {
|
||||
data.append(v.serialize().toString());
|
||||
}
|
||||
|
||||
return forwardSpecific(connection, data.toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private boolean forwardSpecific(BackendServer connection, byte[] data) {
|
||||
return connection.sendPluginMessage(channel, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
if (cache instanceof FileVoteCache) {
|
||||
try {
|
||||
((FileVoteCache) cache).halt();
|
||||
} catch (IOException e) {
|
||||
plugin.getPluginLogger().error("Unable to save cached votes, votes will be lost.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onServerConnect(BackendServer server) {
|
||||
if (cache == null) return;
|
||||
final Collection<Vote> cachedVotes = cache.evict(server.getName());
|
||||
dumpVotesToServer(cachedVotes, server, "server '" + server + "'", failedVotes -> {
|
||||
for (Vote v : failedVotes) cache.addToCache(v, server.getName());
|
||||
});
|
||||
}
|
||||
|
||||
protected void attemptToAddToCache(Vote v, String server) {
|
||||
if (cache != null) {
|
||||
cache.addToCache(v, server);
|
||||
if (plugin.isDebug()) plugin.getPluginLogger().info("Added to forwarding cache: " + v + " -> " + server);
|
||||
} else if (plugin.isDebug())
|
||||
plugin.getPluginLogger()
|
||||
.error("Could not immediately send vote to backend, vote lost! " + v + " -> " + server);
|
||||
}
|
||||
|
||||
protected void attemptToAddToPlayerCache(Vote v, String player) {
|
||||
if (cache != null) {
|
||||
cache.addToCachePlayer(v, player);
|
||||
if (plugin.isDebug())
|
||||
plugin.getPluginLogger().info("Added to forwarding cache: " + v + " -> (player) " + player);
|
||||
} else if (plugin.isDebug())
|
||||
plugin.getPluginLogger()
|
||||
.error("Could not immediately send vote to backend, vote lost! " + v + " -> (player) " + player);
|
||||
}
|
||||
|
||||
// returns a collection of failed votes
|
||||
private void dumpVotesToServer(
|
||||
Collection<Vote> cachedVotes, BackendServer target, String identifier, Consumer<Collection<Vote>> cb) {
|
||||
dumpVotesToServer(cachedVotes, target, identifier, 0, cb);
|
||||
}
|
||||
|
||||
private void dumpVotesToServer(
|
||||
Collection<Vote> cachedVotes,
|
||||
BackendServer target,
|
||||
String identifier,
|
||||
int evictedAlready,
|
||||
Consumer<Collection<Vote>> cb) {
|
||||
if (!cachedVotes.isEmpty()) {
|
||||
plugin.getScheduler()
|
||||
.delayedOnPool(
|
||||
() -> {
|
||||
int evicted = 0;
|
||||
Iterator<Vote> vi = cachedVotes.iterator();
|
||||
Collection<Vote> chunk = new ArrayList<>(dumpRate);
|
||||
while (vi.hasNext() && evicted < dumpRate) {
|
||||
chunk.add(vi.next());
|
||||
vi.remove();
|
||||
}
|
||||
|
||||
if (forwardSpecific(target, chunk)) {
|
||||
evicted += chunk.size();
|
||||
|
||||
if (evicted >= dumpRate && !cachedVotes.isEmpty()) {
|
||||
// if we evicted everything we could but still need to evict more
|
||||
dumpVotesToServer(
|
||||
cachedVotes, target, identifier, evictedAlready + evicted, cb);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// so since our forwarding failed, break like we are done
|
||||
cachedVotes.addAll(chunk);
|
||||
}
|
||||
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger()
|
||||
.info("Successfully evicted " + (evictedAlready + evicted) + " votes to "
|
||||
+ identifier + ".");
|
||||
if (!cachedVotes.isEmpty()) {
|
||||
plugin.getPluginLogger()
|
||||
.info("Held " + cachedVotes.size() + " votes for " + identifier + ".");
|
||||
}
|
||||
}
|
||||
|
||||
// cachedVotes contains any votes which have yet to be evicted
|
||||
cb.accept(cachedVotes);
|
||||
},
|
||||
evictedAlready == 0 ? 3 : 1,
|
||||
TimeUnit.SECONDS);
|
||||
} else {
|
||||
cb.accept(cachedVotes);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handlePlayerSwitch(BackendServer server, String playerName) {
|
||||
if (cache == null) return;
|
||||
if (!serverFilter.isAllowed(server.getName())) return;
|
||||
|
||||
final Collection<Vote> cachedVotes = cache.evictPlayer(playerName);
|
||||
dumpVotesToServer(cachedVotes, server, "player '" + playerName + "'", failedVotes -> {
|
||||
for (Vote v : failedVotes) cache.addToCachePlayer(v, playerName);
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.sponge.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
@@ -13,5 +13,4 @@ public interface ForwardedVoteListener {
|
||||
* @param v Vote that was forwarded
|
||||
*/
|
||||
void onForward(Vote v);
|
||||
|
||||
}
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
/**
|
||||
* Represents a method at which to receive forwarded votes.
|
||||
@@ -9,5 +9,4 @@ public interface ForwardingVoteSink {
|
||||
* Stop or close any oustanding network interfaces. Occurs on the onDisable method of a plugin.
|
||||
*/
|
||||
void halt();
|
||||
|
||||
}
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
@@ -18,5 +18,4 @@ public interface ForwardingVoteSource {
|
||||
* Stop or close any oustanding network interfaces. Occurs on the onDisable method of a plugin.
|
||||
*/
|
||||
void halt();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class ServerFilter {
|
||||
|
||||
public ServerFilter(Collection<String> names, boolean whitelist) {
|
||||
this.names = names;
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
public ServerFilter() {
|
||||
this(Collections.emptySet(), true);
|
||||
}
|
||||
|
||||
private final Collection<String> names;
|
||||
private final boolean whitelist;
|
||||
|
||||
public boolean isAllowed(String name) {
|
||||
return names.contains(name) == whitelist;
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class FileVoteCache extends MemoryVoteCache {
|
||||
|
||||
private final LoggingAdapter l;
|
||||
private final File cacheFile;
|
||||
private final ScheduledVotifierTask saveTask;
|
||||
|
||||
public FileVoteCache(final VotifierPlugin plugin, File cacheFile, long voteTTL) throws IOException {
|
||||
super(plugin, voteTTL);
|
||||
this.cacheFile = cacheFile;
|
||||
this.l = plugin.getPluginLogger();
|
||||
|
||||
load();
|
||||
|
||||
saveTask = plugin.getScheduler()
|
||||
.repeatOnPool(
|
||||
() -> {
|
||||
try {
|
||||
save();
|
||||
} catch (IOException e) {
|
||||
l.error("Unable to save cached votes, votes will be lost if you restart.", e);
|
||||
}
|
||||
},
|
||||
3,
|
||||
3,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private static Set<String> keySet(JsonObject object) {
|
||||
Set<String> set = new HashSet<>();
|
||||
for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
|
||||
set.add(entry.getKey());
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private void load() throws IOException {
|
||||
// Load the cache from disk
|
||||
JsonObject object;
|
||||
try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath(), StandardCharsets.UTF_8)) {
|
||||
object = GsonInst.gson.fromJson(reader, JsonObject.class);
|
||||
if (object == null) {
|
||||
// When the input is not malformed but instead empty, this returns null. Simply assume it is empty.
|
||||
object = new JsonObject();
|
||||
}
|
||||
} catch (NoSuchFileException e) {
|
||||
object = new JsonObject();
|
||||
}
|
||||
|
||||
boolean resave = false;
|
||||
|
||||
// First, lets figure out if we are converting from a pre2.3.6 cache
|
||||
if (!object.has("players") || !object.has("servers") || !object.has("version") || object.size() != 3) {
|
||||
JsonObject oldObject = object;
|
||||
object = new JsonObject();
|
||||
object.add("servers", oldObject);
|
||||
object.add("players", new JsonObject());
|
||||
object.addProperty("version", 2);
|
||||
resave = true;
|
||||
}
|
||||
|
||||
if (object.get("version").getAsInt() != 2)
|
||||
throw new IllegalStateException("Could not read cache file! Unknown version '"
|
||||
+ object.get("version").getAsInt() + "' read.");
|
||||
|
||||
JsonObject players = object.getAsJsonObject("players");
|
||||
JsonObject servers = object.getAsJsonObject("servers");
|
||||
|
||||
for (String player : keySet(players)) {
|
||||
playerVoteCache.put(player, readVotes(players.getAsJsonArray(player)));
|
||||
}
|
||||
|
||||
for (String server : keySet(servers)) {
|
||||
voteCache.put(server, readVotes(servers.getAsJsonArray(server)));
|
||||
}
|
||||
|
||||
if (resave) {
|
||||
File replacementFile;
|
||||
for (int i = 0; ; i++) {
|
||||
replacementFile = new File(cacheFile.getParentFile(), cacheFile.getName() + ".bak." + i);
|
||||
if (!replacementFile.exists()) break;
|
||||
}
|
||||
|
||||
if (!cacheFile.renameTo(replacementFile)) {
|
||||
l.error("Backup movement failed! Will not save.");
|
||||
return;
|
||||
}
|
||||
|
||||
l.warn("Saving new vote cache format to file - backup moved to " + replacementFile.getAbsolutePath());
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<VoteWithRecordedTimestamp> readVotes(JsonArray voteArray) {
|
||||
Collection<VoteWithRecordedTimestamp> votes = new HashSet<>(voteArray.size());
|
||||
for (int i = 0; i < voteArray.size(); i++) {
|
||||
JsonObject voteObject = voteArray.get(i).getAsJsonObject();
|
||||
VoteWithRecordedTimestamp v = new VoteWithRecordedTimestamp(voteObject);
|
||||
if (hasTimedOut(v)) l.warn("Purging out of date vote.", v);
|
||||
else votes.add(v);
|
||||
}
|
||||
return votes;
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
JsonObject votesObject = new JsonObject();
|
||||
|
||||
cacheLock.lock();
|
||||
try {
|
||||
votesObject.addProperty("version", 2);
|
||||
votesObject.add("players", serializeMap(playerVoteCache));
|
||||
votesObject.add("servers", serializeMap(voteCache));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(cacheFile.toPath(), StandardCharsets.UTF_8)) {
|
||||
GsonInst.gson.toJson(votesObject, writer);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonObject serializeMap(Map<String, Collection<VoteWithRecordedTimestamp>> map) {
|
||||
JsonObject o = new JsonObject();
|
||||
|
||||
Iterator<Map.Entry<String, Collection<VoteWithRecordedTimestamp>>> entryItr =
|
||||
map.entrySet().iterator();
|
||||
while (entryItr.hasNext()) {
|
||||
Map.Entry<String, Collection<VoteWithRecordedTimestamp>> entry = entryItr.next();
|
||||
JsonArray array = new JsonArray();
|
||||
Iterator<VoteWithRecordedTimestamp> voteItr = entry.getValue().iterator();
|
||||
while (voteItr.hasNext()) {
|
||||
VoteWithRecordedTimestamp vote = voteItr.next();
|
||||
|
||||
// if the vote is no longer valid, notify and remove
|
||||
if (hasTimedOut(vote)) {
|
||||
l.warn("Purging out of date vote.", vote);
|
||||
voteItr.remove();
|
||||
} else {
|
||||
array.add(vote.serialize());
|
||||
}
|
||||
}
|
||||
|
||||
// if, during our iteration, we TTL invalidated all of the votes
|
||||
if (entry.getValue().isEmpty()) entryItr.remove();
|
||||
o.add(entry.getKey(), array);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
public void halt() throws IOException {
|
||||
saveTask.cancel();
|
||||
save();
|
||||
}
|
||||
}
|
||||
Vendored
+174
@@ -0,0 +1,174 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class MemoryVoteCache implements VoteCache {
|
||||
|
||||
private final LoggingAdapter l;
|
||||
private final long voteTTL;
|
||||
|
||||
protected final Map<String, Collection<VoteWithRecordedTimestamp>> voteCache;
|
||||
protected final Map<String, Collection<VoteWithRecordedTimestamp>> playerVoteCache;
|
||||
|
||||
protected final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
private final ScheduledVotifierTask sweepTask;
|
||||
|
||||
public MemoryVoteCache(VotifierPlugin p, long voteTTL) {
|
||||
voteCache = new HashMap<>();
|
||||
playerVoteCache = new HashMap<>();
|
||||
|
||||
this.voteTTL = voteTTL;
|
||||
|
||||
this.l = p.getPluginLogger();
|
||||
this.sweepTask = p.getScheduler().repeatOnPool(this::sweep, 12, 12, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getCachedServers() {
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return Collections.unmodifiableCollection(new ArrayList<>(voteCache.keySet()));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCache(Vote v, String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
voteCache.computeIfAbsent(server, k -> new HashSet<>()).add(new VoteWithRecordedTimestamp(v));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCachePlayer(Vote v, String player) {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
playerVoteCache.computeIfAbsent(player, k -> new HashSet<>()).add(new VoteWithRecordedTimestamp(v));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evictPlayer(String player) {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
Collection<VoteWithRecordedTimestamp> playerVotes = playerVoteCache.remove(player);
|
||||
if (playerVotes != null) {
|
||||
return new HashSet<>(playerVotes);
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evict(String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
Collection<VoteWithRecordedTimestamp> serverVotes = voteCache.remove(server);
|
||||
if (serverVotes != null) {
|
||||
return new HashSet<>(serverVotes);
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void sweep() {
|
||||
cacheLock.lock();
|
||||
try {
|
||||
sweep(voteCache);
|
||||
sweep(playerVoteCache);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void sweep(Map<String, Collection<VoteWithRecordedTimestamp>> m) {
|
||||
for (Map.Entry<String, Collection<VoteWithRecordedTimestamp>> entry : m.entrySet()) {
|
||||
Iterator<VoteWithRecordedTimestamp> vi = entry.getValue().iterator();
|
||||
while (vi.hasNext()) {
|
||||
VoteWithRecordedTimestamp v = vi.next();
|
||||
if (hasTimedOut(v)) {
|
||||
l.warn("Purging out of date vote.", v);
|
||||
vi.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasTimedOut(VoteWithRecordedTimestamp v) {
|
||||
if (voteTTL == -1) return false;
|
||||
|
||||
long daysAsMillis = TimeUnit.DAYS.toMillis(voteTTL);
|
||||
return v.recorded + daysAsMillis < System.currentTimeMillis();
|
||||
}
|
||||
|
||||
static class VoteWithRecordedTimestamp extends Vote {
|
||||
private final long recorded;
|
||||
|
||||
VoteWithRecordedTimestamp(Vote vote) {
|
||||
super(vote);
|
||||
this.recorded = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
VoteWithRecordedTimestamp(JsonObject object) {
|
||||
super(object);
|
||||
if (object.has("recorded")) {
|
||||
this.recorded = object.get("recorded").getAsLong();
|
||||
} else {
|
||||
// Assign the current time, in the hope that the stale votes will eventually get purged.
|
||||
this.recorded = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject serialize() {
|
||||
JsonObject object = super.serialize();
|
||||
object.addProperty("recorded", this.recorded);
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
|
||||
VoteWithRecordedTimestamp that = (VoteWithRecordedTimestamp) o;
|
||||
|
||||
return recorded == that.recorded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + (int) (recorded ^ (recorded >>> 32));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-5
@@ -1,7 +1,6 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -32,8 +31,7 @@ public interface VoteCache {
|
||||
* @param v Vote to add to the cache
|
||||
* @param player Server to add vote under
|
||||
*/
|
||||
default void addToCachePlayer(Vote v, String player) {
|
||||
}
|
||||
default void addToCachePlayer(Vote v, String player) {}
|
||||
|
||||
/**
|
||||
* Evicts all votes from the vote cache and returns a collection of vote objects
|
||||
@@ -51,6 +49,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();
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2Encoder;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierResponseHandler;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.Delimiters;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
private static final int MAX_RETRIES = 5;
|
||||
private final VotifierPlugin plugin;
|
||||
private final Supplier<Bootstrap> nettyBootstrap;
|
||||
private final List<BackendServer> backendServers;
|
||||
private final VoteCache voteCache;
|
||||
|
||||
private static final StringDecoder STRING_DECODER = new StringDecoder(StandardCharsets.US_ASCII);
|
||||
|
||||
public ProxyForwardingVoteSource(
|
||||
VotifierPlugin plugin,
|
||||
Supplier<Bootstrap> nettyBootstrap,
|
||||
List<BackendServer> backendServers,
|
||||
VoteCache voteCache) {
|
||||
this.plugin = plugin;
|
||||
this.nettyBootstrap = nettyBootstrap;
|
||||
this.backendServers = backendServers;
|
||||
this.voteCache = voteCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
for (final BackendServer server : backendServers) {
|
||||
forwardVote(server, v, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private int fib(int t) {
|
||||
if (t <= 1) return 1;
|
||||
return fib(t - 2) + fib(t - 1);
|
||||
}
|
||||
|
||||
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
|
||||
nettyBootstrap
|
||||
.get()
|
||||
.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel channel) {
|
||||
channel.pipeline()
|
||||
.addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
|
||||
channel.pipeline().addLast(STRING_DECODER);
|
||||
channel.pipeline().addLast(new VotifierProtocol2Encoder(server.key));
|
||||
channel.pipeline()
|
||||
.addLast(new VotifierProtocol2HandshakeHandler(
|
||||
v,
|
||||
new VotifierResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger()
|
||||
.info("Successfully forwarded vote " + v + " to "
|
||||
+ server.address + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable error) {
|
||||
handleFailure(server, v, error, tries);
|
||||
}
|
||||
},
|
||||
plugin));
|
||||
}
|
||||
})
|
||||
.connect(server.address)
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleFailure(final BackendServer server, final Vote v, Throwable cause, final int tries) {
|
||||
int nextDelay = fib(tries + 1);
|
||||
boolean willRetry = tries < MAX_RETRIES;
|
||||
|
||||
String msg = "Unable to send vote to " + server.address + ".";
|
||||
if (willRetry) {
|
||||
msg += " Will retry sending in " + nextDelay + " second(s).";
|
||||
} else {
|
||||
if (voteCache == null) {
|
||||
msg += " This vote will be lost!";
|
||||
} else {
|
||||
voteCache.addToCache(v, server.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger().error(msg, cause);
|
||||
} else {
|
||||
plugin.getPluginLogger().error(msg);
|
||||
}
|
||||
|
||||
if (willRetry) {
|
||||
plugin.getScheduler().delayedOnPool(() -> forwardVote(server, v, tries + 1), nextDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {}
|
||||
|
||||
public static class BackendServer {
|
||||
private final String name;
|
||||
private final InetSocketAddress address;
|
||||
private final Key key;
|
||||
|
||||
public BackendServer(String name, InetSocketAddress address, Key key) {
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-5
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
@@ -21,9 +21,6 @@ public class VoteRequest {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VoteRequest{" +
|
||||
"challenge='" + challenge + '\'' +
|
||||
", vote=" + vote +
|
||||
'}';
|
||||
return "VoteRequest{" + "challenge='" + challenge + '\'' + ", vote=" + vote + '}';
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -1,15 +1,14 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.Mac;
|
||||
|
||||
public class VotifierProtocol2Encoder extends MessageToByteEncoder<VoteRequest> {
|
||||
private static final short MAGIC = 0x733A;
|
||||
+8
-8
@@ -1,7 +1,7 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
@@ -9,16 +9,17 @@ import io.netty.handler.codec.CorruptedFrameException;
|
||||
public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final Vote toSend;
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
private final NuVotifier nuVotifier;
|
||||
private final VotifierPlugin nuVotifier;
|
||||
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, NuVotifier nuVotifier) {
|
||||
public VotifierProtocol2HandshakeHandler(
|
||||
Vote toSend, VotifierResponseHandler responseHandler, VotifierPlugin nuVotifier) {
|
||||
this.toSend = toSend;
|
||||
this.responseHandler = responseHandler;
|
||||
this.nuVotifier = nuVotifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String s) {
|
||||
String[] handshakeContents = s.split(" ");
|
||||
if (handshakeContents.length != 3) {
|
||||
throw new CorruptedFrameException("Handshake is not valid.");
|
||||
@@ -26,16 +27,15 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
|
||||
|
||||
VoteRequest request = new VoteRequest(handshakeContents[2], toSend);
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Sent request: " + request.toString());
|
||||
nuVotifier.getPluginLogger().info("Sent request: " + request.toString());
|
||||
}
|
||||
ctx.writeAndFlush(request);
|
||||
ctx.pipeline().addLast(new VotifierProtocol2ResponseHandler(responseHandler));
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
+6
-5
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
@@ -13,20 +13,21 @@ public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandle
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||
JsonObject object = GsonInst.gson.fromJson(msg, JsonObject.class);
|
||||
String status = object.get("status").getAsString();
|
||||
if (status.equals("ok")) {
|
||||
responseHandler.onSuccess();
|
||||
} else {
|
||||
responseHandler.onFailure(new Exception("Remote server error: " + object.get("cause").getAsString() +
|
||||
": " + object.get("error").getAsString()));
|
||||
responseHandler.onFailure(
|
||||
new Exception("Remote server error: " + object.get("cause").getAsString() + ": "
|
||||
+ object.get("error").getAsString()));
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
public interface VotifierResponseHandler {
|
||||
void onSuccess();
|
||||
@@ -0,0 +1,60 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,4 @@ import com.google.gson.GsonBuilder;
|
||||
public class GsonInst {
|
||||
|
||||
public static final Gson gson = new GsonBuilder().create();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class KeyCreator {
|
||||
public static Key createKeyFrom(String token) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,7 @@ import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class TokenUtil {
|
||||
private TokenUtil() {
|
||||
|
||||
}
|
||||
private TokenUtil() {}
|
||||
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
|
||||
+33
-42
@@ -1,65 +1,50 @@
|
||||
package com.vexsoftware.votifier.util.standalone;
|
||||
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledExecutorServiceVotifierScheduler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
|
||||
public class StandaloneVotifierPlugin implements VotifierPlugin {
|
||||
private final Map<String, Key> tokens;
|
||||
private final VoteReceiver receiver;
|
||||
private final KeyPair v1Key;
|
||||
private final InetSocketAddress bind;
|
||||
private NioEventLoopGroup group;
|
||||
private final VotifierScheduler scheduler;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
public StandaloneVotifierPlugin(Map<String, Key> tokens, VoteReceiver receiver, KeyPair v1Key, InetSocketAddress bind) {
|
||||
public StandaloneVotifierPlugin(
|
||||
Map<String, Key> tokens, VoteReceiver receiver, KeyPair v1Key, InetSocketAddress bind) {
|
||||
this.receiver = receiver;
|
||||
this.bind = bind;
|
||||
this.tokens = Collections.unmodifiableMap(new HashMap<>(tokens));
|
||||
this.v1Key = v1Key;
|
||||
this.scheduler = new ScheduledExecutorServiceVotifierScheduler(Executors.newScheduledThreadPool(1));
|
||||
}
|
||||
|
||||
public Throwable start() throws Throwable {
|
||||
group = new NioEventLoopGroup(2);
|
||||
public void start() {
|
||||
start(o -> {});
|
||||
}
|
||||
|
||||
ChannelFuture future = new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(group)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(StandaloneVotifierPlugin.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, v1Key != null));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(StandaloneVotifierPlugin.this));
|
||||
}
|
||||
})
|
||||
.bind(bind)
|
||||
.syncUninterruptibly();
|
||||
|
||||
if (!future.isSuccess()) {
|
||||
return future.cause();
|
||||
} else {
|
||||
return null;
|
||||
public void start(Consumer<Throwable> error) {
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
}
|
||||
this.bootstrap = new VotifierServerBootstrap(bind.getHostString(), bind.getPort(), this, v1Key == null);
|
||||
this.bootstrap.start(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,17 +58,23 @@ public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.3.5";
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return new JavaUtilLogger(Logger.getAnonymousLogger());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress)
|
||||
throws Exception {
|
||||
receiver.onVote(vote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
|
||||
receiver.onException(throwable);
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,6 @@ import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
public interface VoteReceiver {
|
||||
void onVote(Vote vote) throws Exception;
|
||||
|
||||
void onException(Throwable throwable);
|
||||
}
|
||||
-1
@@ -2,7 +2,6 @@ package com.vexsoftware.votifier.util.standalone;
|
||||
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.Key;
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.*;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class TestVotifierPlugin implements VotifierPlugin {
|
||||
private static final byte[] PUBLIC_KEY;
|
||||
private static final byte[] PRIVATE_KEY;
|
||||
|
||||
static byte[] r(String u) throws Exception {
|
||||
URL resourceUrl = TestVotifierPlugin.class.getResource(u);
|
||||
Path resourcePath = Paths.get(resourceUrl.toURI());
|
||||
|
||||
return Base64.getDecoder().decode(new String(Files.readAllBytes(resourcePath), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
PUBLIC_KEY = r("/public.key");
|
||||
PRIVATE_KEY = r("/private.key");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static final TestVotifierPlugin I = new TestVotifierPlugin();
|
||||
|
||||
public static TestVotifierPlugin getI() {
|
||||
return I;
|
||||
}
|
||||
|
||||
private final Map<String, Key> keyMap = new HashMap<>();
|
||||
private final KeyPair keyPair;
|
||||
|
||||
public TestVotifierPlugin() {
|
||||
try {
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(
|
||||
PUBLIC_KEY);
|
||||
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
|
||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
|
||||
PRIVATE_KEY);
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
|
||||
keyPair = new KeyPair(publicKey, privateKey);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
keyMap.put("default", KeyCreator.createKeyFrom("test"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Key> getTokens() {
|
||||
return keyMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair getProtocolV1Key() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.3";
|
||||
}
|
||||
|
||||
public void specificKeysOnly() {
|
||||
keyMap.clear();
|
||||
keyMap.put("Test", KeyCreator.createKeyFrom("test"));
|
||||
}
|
||||
|
||||
public void restoreDefault() {
|
||||
keyMap.clear();
|
||||
keyMap.put("default", KeyCreator.createKeyFrom("test"));
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class VoteUtil {
|
||||
public static byte[] encodePOJOv1(Vote vote) throws Exception {
|
||||
return encodePOJOv1(vote, TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
|
||||
}
|
||||
|
||||
public static byte[] encodePOJOv1(Vote vote, PublicKey key) throws Exception {
|
||||
List<String> ordered = new ArrayList<>();
|
||||
ordered.add("VOTE");
|
||||
ordered.add(vote.getServiceName());
|
||||
ordered.add(vote.getUsername());
|
||||
ordered.add(vote.getAddress());
|
||||
ordered.add(vote.getTimeStamp());
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String s : ordered) {
|
||||
builder.append(s).append('\n'); // naive join needed!
|
||||
}
|
||||
|
||||
return RSA.encrypt(builder.toString().getBytes(StandardCharsets.UTF_8), key);
|
||||
}
|
||||
}
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
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 java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class VotifierProtocol1DecoderTest {
|
||||
private static final VotifierSession SESSION = new VotifierSession();
|
||||
|
||||
private EmbeddedChannel createChannel() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocol1Decoder());
|
||||
channel.attr(VotifierSession.KEY).set(SESSION);
|
||||
channel.attr(VotifierPlugin.KEY).set(TestVotifierPlugin.getI());
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulDecode() throws Exception {
|
||||
Vote votePojo = new Vote("Test", "test", "test", "test");
|
||||
|
||||
// Send the vote
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
byte[] encrypted = VoteUtil.encodePOJOv1(votePojo);
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
assertTrue(channel.writeInbound(encryptedByteBuf));
|
||||
Object presumedVote = channel.readInbound();
|
||||
assertEquals(votePojo, presumedVote);
|
||||
assertFalse(channel.finish());
|
||||
}
|
||||
|
||||
private void verifyFailure(String bad) throws Exception {
|
||||
// Send the bad vote
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
byte[] encrypted = RSA.encrypt(bad.getBytes(StandardCharsets.UTF_8), TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
try {
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
public void testFailureDecodeMissingField() throws Exception {
|
||||
verifyFailure("VOTE\nTest\ntest\ntest"); // missing field
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
public void testFailureDecodeBadOpcode() throws Exception {
|
||||
verifyFailure("TEST\nTest\ntest\ntest\ntest\n");
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
public void testFailureDecodeBadRsa() throws Exception {
|
||||
// Decode our bad RSA key
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(TestVotifierPlugin.r("/bad_public.key"));
|
||||
PublicKey badPublicKey = keyFactory.generatePublic(publicKeySpec);
|
||||
|
||||
// Send the bad vote
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
byte[] encrypted = VoteUtil.encodePOJOv1(new Vote("Test", "test", "test", "test"), badPublicKey);
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
try {
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class VotifierProtocol2DecoderTest {
|
||||
private static final VotifierSession SESSION = new VotifierSession();
|
||||
|
||||
private EmbeddedChannel createChannel() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocol2Decoder());
|
||||
channel.attr(VotifierSession.KEY).set(SESSION);
|
||||
channel.attr(VotifierPlugin.KEY).set(TestVotifierPlugin.getI());
|
||||
return channel;
|
||||
}
|
||||
|
||||
private void sendVote(Vote vote, Key key, boolean expectSuccess) throws Exception {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
JSONObject object = new JSONObject();
|
||||
JsonObject payload = vote.serialize();
|
||||
payload.addProperty("challenge", SESSION.getChallenge());
|
||||
String payloadEncoded = GsonInst.gson.toJson(payload);
|
||||
object.put("payload", payloadEncoded);
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
object.put("signature",
|
||||
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
if (expectSuccess) {
|
||||
assertTrue(channel.writeInbound(object.toString()));
|
||||
assertEquals(vote, channel.readInbound());
|
||||
assertFalse(channel.finish());
|
||||
} else {
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulDecode() throws Exception {
|
||||
sendVote(new Vote("Test", "test", "test", "0"), TestVotifierPlugin.getI().getTokens().get("default"), true);
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void testFailureDecodeBadPacket() throws Exception {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JsonObject payload = vote.serialize();
|
||||
payload.addProperty("challenge", SESSION.getChallenge());
|
||||
object.put("payload", GsonInst.gson.toJson(payload));
|
||||
// We "forget" the signature.
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void testFailureDecodeBadVoteField() throws Exception {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JsonObject payload = vote.serialize();
|
||||
String payloadEncoded = GsonInst.gson.toJson(payload);
|
||||
// We "forget" the challenge.
|
||||
object.put("payload", payloadEncoded);
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(TestVotifierPlugin.getI().getTokens().get("default"));
|
||||
object.put("signature",
|
||||
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void testFailureDecodeBadChallenge() throws Exception {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JsonObject payload = vote.serialize();
|
||||
// We provide the wrong challenge.
|
||||
payload.addProperty("challenge", "not a challenge for me");
|
||||
object.put("payload", payload.toString());
|
||||
String payloadEncoded = GsonInst.gson.toJson(payload);
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(TestVotifierPlugin.getI().getTokens().get("default"));
|
||||
object.put("signature",
|
||||
Base64.getEncoder().encode(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
public void testFailureDecodeBadSignature() throws Exception {
|
||||
Vote vote = new Vote("Bad Service", "test", "test", "0");
|
||||
sendVote(vote, KeyCreator.createKeyFrom("BadKey"), false);
|
||||
}
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class VotifierProtocolDifferentiatorTest {
|
||||
@Test
|
||||
public void v1Test() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, true));
|
||||
|
||||
VotifierSession session = new VotifierSession();
|
||||
channel.attr(VotifierSession.KEY).set(session);
|
||||
|
||||
ByteBuf test = Unpooled.buffer(256);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
test.writeByte(0);
|
||||
}
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void v2Test() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
|
||||
|
||||
VotifierSession session = new VotifierSession();
|
||||
channel.attr(VotifierSession.KEY).set(session);
|
||||
|
||||
ByteBuf test = Unpooled.buffer();
|
||||
test.writeShort(0x733A);
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.TWO, session.getVersion());
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void failIfv1NotSupported() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
|
||||
|
||||
VotifierSession session = new VotifierSession();
|
||||
channel.attr(VotifierSession.KEY).set(session);
|
||||
|
||||
ByteBuf test = Unpooled.buffer(256);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
test.writeByte(0);
|
||||
}
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void failOnBadPacketTest() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
|
||||
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
buf.writeByte(0);
|
||||
}
|
||||
|
||||
try {
|
||||
channel.writeInbound(buf);
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tryIdentifyRealVotev1() throws Exception {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, true));
|
||||
|
||||
VotifierSession session = new VotifierSession();
|
||||
channel.attr(VotifierSession.KEY).set(session);
|
||||
|
||||
Vote votePojo = new Vote("Test", "test", "test", "test");
|
||||
byte[] encrypted = VoteUtil.encodePOJOv1(votePojo);
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArrOsFuGPoHDZEFRHXriZRqrUXXjUOhVabC0Xu+WPtVbQrvNngN0igo0ZJIZELflhsFNBUJAnREo+wColfDg1rk2juEUYDt+gRzrN3WiqgzZolF3E4Dg4LrXh6cPuu4AeOZW9CtjXt2JwPBhlWLZb2RFhA6FziFLeMBa42/5VLFCQAmiuuU3OCiBskR3Jz/E5zqc7V/j247OMm+WRJ/k8OFTEFB+zFINOZY1ud3ltwkzQIPi1luGNW8Yt8+CEg/6hDiVlpFwgsazt4uKI/UfU5oP0kAKjQUfFGvNaBtJ0RlsBeJ7VDpOfIG6dDb8pe4IaA9aJLVcXWEn30pHsyYl2NwIDAQAB
|
||||
@@ -1 +0,0 @@
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCd5osoNvoLM52pN8U15QkRXpGVxk0WaoNLWgN24pnW30FIAO3bp8qD2FXJzX2hvw43LIwudVEbwmdzeytVWDNuigLqEsJJsXCHh9ORKjGVHvS240r6idfxJ139Jz2d3oSZG8acHdHgs/H/M98O8lz9Zb2KHtm+rxpiwsV2SdhMtfMDJmRx4p0JklgJ8TV1ZApoypbGtRKXHOirVSs6zfgaYaeglUNITAgsZZNZk+j9Pp572FxyYryxkUaWVn09RAD07XvWKJc3f7NBElA2LpuZXTYJh3BXaI/N05tdgPej5o99PXaA2UMhdqo778zoQoeWzOvbNMUqZROqGW5WDwhdAgMBAAECggEAM8xcXB3k2uXc05UrCaoP1nXadnKt+SuJ1J87wbVGWDm21aMQZF1fzu6Z/fB+RSBCzpC7j8YGjH+ypiyCo6WigaGeoSD0QW6HBr7lBZr81vlnt9NKVnP/tel/KLvFt9M2Vm/JMI+LuRXuzhQ7GVEidjH5AqVqMuaIVlukoVBKN6d20KihcHeCjOZLAoL727QYGrHNr0eH9wQpgMzj3A51ubi5ma3bDvnSvN4ZoP3BhmDTRVnHEh4hx/XFYELi3RDGN2SDjegNXlZ+e03BHCGRryOCuwY1eua78gkrWN8sr3QAdWhEN/Pdwq0BpVsdsbyPzmgoamAMmBbKyBnEJxuvlQKBgQDtLQIhFVYA7vNtpwnXV3ahyEPCNf+tLJYsLYjKWRpoTw6bI3FfgMAEPGbDXLsmLi37mvRmCJpoU9j4nfRd/umoEIW6xuaYDdGRpyaiOlkEXyJd0n3uv/3mF7VtyltrPK3Mt2Gb7kGaZqe07B4a5UC02CCvpKqpbFfC0XxHP32GkwKBgQCqbs2D/pNcdA9iHm+AhX/oylK+BfiruTvLx/c2wOFleQOiNZUZCC6zELIDzuQXPlk9mlNshnciIItcjnyLGkDr2lvJpt6Bi65Jx5hYhlWrms883/hRUSjgKtJ9PFWl8wUVTJJsrOg0fm2ZM+YXpO9AvziRTEPj2MeibQjjlfsbTwKBgHc1ZBiUzMFzWM8IYh3mTC5MpiH7WiMEggZ6bIKn/mnnMoQvcFB8OgbnBQ+0kCxEkcAgxi5yrIk7e5aabbTA51gd1dlnF4fdijQXO4fWo99aob9eiQVzYn4/9GTWjNR9jy2SDnF9c2PTrP92njsOd1NuPh+KOG0nk4TMHhcBhmoZAoGAF4ZPy8ZZ5MsITIzS6zSCIUm7/gqo9lsNC+XAE8iv0+fOPpeSyu+8Mm89gxUNmr+6Zaxlk3dD/mvrkI0CV9j52RQ80yq7T7pOBlUMjDuOm680p7xqdB71u2ttvmeTkuJmczgshd+f9Y715c2qFt1nQNKkJjoWrN6BP8Q5E+Dh1jUCgYEApBuFGwmcSob6rIzinM4nJRyMLXqIy6nKCy8bBLkWCFPGztjIxhKBA/pErADhkLaLyM5reeMPNBbxruAFJ3mzfOneKf9lt0oHq2/jq0vJvYUj9tZjD2Sw6dhj0JqQhKIkhux1rU1xAAedY21JGTS+Be0dFfSv5uDrErPJiN+Qyt0=
|
||||
@@ -1 +0,0 @@
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAneaLKDb6CzOdqTfFNeUJEV6RlcZNFmqDS1oDduKZ1t9BSADt26fKg9hVyc19ob8ONyyMLnVRG8Jnc3srVVgzbooC6hLCSbFwh4fTkSoxlR70tuNK+onX8Sdd/Sc9nd6EmRvGnB3R4LPx/zPfDvJc/WW9ih7Zvq8aYsLFdknYTLXzAyZkceKdCZJYCfE1dWQKaMqWxrUSlxzoq1UrOs34GmGnoJVDSEwILGWTWZPo/T6ee9hccmK8sZFGllZ9PUQA9O171iiXN3+zQRJQNi6bmV02CYdwV2iPzdObXYD3o+aPfT12gNlDIXaqO+/M6EKHlszr2zTFKmUTqhluVg8IXQIDAQAB
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
|
||||
<module name="FileTabCharacter">
|
||||
<property name="eachLine" value="true"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<module name="FinalLocalVariable"/>
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="Indentation">
|
||||
<property name="severity" value="ignore"/>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="\borg\.junit\.jupiter\.api\.Assertions\.fail\s*\(\s*"Not yet implemented"\s*\)\s*;"/>
|
||||
<property name="message" value="Remove placeholder fail() and implement the test."/>
|
||||
<property name="ignoreCase" value="true"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="(?<!\w)fail\s*\(\s*"Not yet implemented"\s*\)\s*;"/>
|
||||
<property name="message" value="Remove placeholder fail() and implement the test."/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="23">
|
||||
<profile kind="CodeFormatterProfile" name="TrueOG Network Formatting" version="23">
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case_after_arrow" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_arrows_in_switch_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow" value="20"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="next_line_on_wrap"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
@@ -1,80 +0,0 @@
|
||||
This document outlines various developer resources concerning NuVotifier.
|
||||
|
||||
# Contributing
|
||||
|
||||
NuVotifier is an open source GNU GPLv3 licensed project. We accept contributions
|
||||
through pull requests, and will make sure to credit you for your gracious
|
||||
contribution.
|
||||
|
||||
# Building NuVotifier
|
||||
|
||||
NuVotifier can be built by running the following: `./gradlew build` or
|
||||
`./gradlew.bat build` on Windows. The resultant universal jar is built and
|
||||
written to `universal/build/libs/nuvotifier-{version}.jar`.
|
||||
|
||||
The build directories can be cleaned instead using the `./gradlew clean` (and
|
||||
`./gradlew.bat build`) command.
|
||||
|
||||
# NuVotifier as a plugin dependency
|
||||
|
||||
ParallelBlock now maintains an independent Maven repository for free use by the
|
||||
community. Builds are automatically published by a CI operated by ParallelBlock.
|
||||
|
||||
## Maven
|
||||
|
||||
You can have your project depend on NuVotifier as a dependency through the
|
||||
following code snippets:
|
||||
|
||||
```
|
||||
<project>
|
||||
...
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>parallelblock-public</id>
|
||||
<name>ParallelBlock Public Repository</name>
|
||||
<url>https://repo.parallelblock.com/repository/maven-public/</url>
|
||||
</repository>
|
||||
...
|
||||
</repositories>
|
||||
...
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-universal</artifactId>
|
||||
<version>...</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
...
|
||||
</dependencies>
|
||||
...
|
||||
</project>
|
||||
```
|
||||
|
||||
## Gradle
|
||||
|
||||
You can include NuVotifier into your gradle project using the following lines:
|
||||
|
||||
```
|
||||
...
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://repo.parallelblock.com/repository/maven-public/'
|
||||
}
|
||||
...
|
||||
}
|
||||
...
|
||||
dependencies {
|
||||
compileOnly "com.vexsoftware:nuvotifier-universal:verison"
|
||||
...
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
# Implementing NuVotifier v2 protocol in a server list
|
||||
|
||||
NuVotifier provides production ready reference implementations for interacting
|
||||
with NuVotifier servers. These libraries are available below:
|
||||
|
||||
- [NodeJS](https://github.com/NuVotifier/votifier2-js)
|
||||
- [PHP](https://github.com/NuVotifier/votifier2-php)
|
||||
- [Golang](https://github.com/minecrafter/go-votifier)
|
||||
@@ -1,16 +0,0 @@
|
||||
# Documentation
|
||||
|
||||
This directory holds documentation for NuVotifier.
|
||||
|
||||
# Usage Guide
|
||||
|
||||
The usage/configuration guide may be found [here](usage.md).
|
||||
|
||||
# Developer Guide
|
||||
|
||||
The developer guide for NuVotifier may be found [here](developer.md).
|
||||
|
||||
# Technical QA
|
||||
|
||||
Some assorted technical questions not important to most people are found
|
||||
[here](technical_qa.md).
|
||||
@@ -1,78 +0,0 @@
|
||||
This document outlines some of the technical reasons for a new NuVotifier.
|
||||
|
||||
# Why a new protocol?
|
||||
|
||||
The old Votifier protocol has three major flaws, which we feel are corrected in the new protocol.
|
||||
|
||||
* The data is encrypted (with RSA) but not hashed to verify its integrity. The new protocol utilizes HMAC with SHA256 (but no encryption, given that the data sent is eventually revealed in some form or another).
|
||||
* The canonical Votifier protocol is vulnerable to replay attacks.
|
||||
* The canonical Votifier is one key for all clients, which is weak. While NuVotifier still supports a default key (for the less paranoid), this ability can be disabled and force per-server keys.
|
||||
|
||||
# Is NuVotifier a drop-in replacement for Votifier?
|
||||
|
||||
Yes. NuVotifier is compatible with Votifier server lists and Votifier listeners, with some differences:
|
||||
|
||||
* The `Vote` object's setters are deprecated.
|
||||
* Support for standalone vote listeners (`.class` files) has been removed, as handling `VotifierEvent` is more flexible.
|
||||
|
||||
# Protocol Documentation
|
||||
|
||||
This documentation is for server lists that wish to add NuVotifier support.
|
||||
|
||||
There are two versions of the NuVotifier protocol.
|
||||
|
||||
## Handshake
|
||||
|
||||
A connection is made to the NuVotifier server by the server list, and immediately NuVotifier will send its version in the following packet:
|
||||
|
||||
"VOTIFIER <version> <challenge>"
|
||||
|
||||
The challenge will not be present in version 1.x servers.
|
||||
|
||||
## Protocol v2
|
||||
|
||||
NuVotifier expects a message composed of `0x733A` (16-bit big-endian integer) and the length of the following JSON message (as a 16-bit big-endian integer) sent plus the message.
|
||||
|
||||
The message is JSON-encoded containing the following data:
|
||||
|
||||
* `payload` contains the fully encoded vote. It is a JSON message containing the `serviceName` (string), `username` (string), `address` (string, but must be a valid IPv4 or IPv6 address), `timestamp` (a long with the time this vote was taken in _milliseconds_), and `challenge` (from the handshake). It is optional, but recommended to supply a `uuid` field with the player's UUID (with dashes, e.g. `652a2bc4-e8cd-405d-b7b6-98156ee2dc09`).
|
||||
* `signature` is a HMAC-SHA256 digest of the payload with a owner-provided key.
|
||||
|
||||
A response is given after the vote has been received and parsed, but not yet processed.
|
||||
|
||||
For your convenience, the NuVotifier team has provided client libraries in [JavaScript](https://github.com/NuVotifier/votifier2-js), [PHP](https://github.com/NuVotifier/votifier2-php) and [Go](https://github.com/minecrafter/go-votifier). If you are stuck, these libraries may help you.
|
||||
|
||||
## Protocol v1 (deprecated)
|
||||
|
||||
For better compatibility, NuVotifier supports the old Votifier protocol as well.
|
||||
|
||||
NuVotifier expects a 256 byte RSA encrypted block (the public key should be obtained by the NuVotifier user), with the following format:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>VOTE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>serviceName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>username</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>address</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>timeStamp</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
The first string of value "VOTE" is an opcode check to ensure that RSA was encoded and decoded properly, if this value is wrong then NuVotifier assumes that there was a problem with encryption and drops the connection. `serviceName` is the name of the top list service, `username` is the username (entered by the voter) of the person who voted, `address` is the IP address of the voter, and `timeStamp` is the time stamp of the vote. Each string is delimited by the newline character `\n` (byte value 10). The `space` block is the empty space that is left over, **the block must be exactly 256 bytes** regardless of how much information it holds.
|
||||
-231
@@ -1,231 +0,0 @@
|
||||
This document outlines how to configure NuVotifier for a variety of different
|
||||
configurations. It should be used by server administrators looking to set up
|
||||
NuVotifier on their network.
|
||||
|
||||
# Key vs Token
|
||||
|
||||
This document uses the terms `token` and `key`. These are two separate terms,
|
||||
and it is important to understand the differences.
|
||||
|
||||
## Key
|
||||
|
||||
Keys in terms of NuVotifier refer to the old Votifier style RSA keys. They
|
||||
are typically distributed as a pair of files - a public and private key. When
|
||||
dealing with RSA keys, you can share your public key with server lists, but make
|
||||
sure to keep your private key safe with you. You can find these keys
|
||||
auto-generated under `plugins/(Nu)Votifier/rsa/`, where your public key is
|
||||
named `public.key`, while your private key is labeled `private.key`. Make sure
|
||||
not to separate the two, as they won't work together.
|
||||
|
||||
## Token
|
||||
|
||||
Tokens in terms of NuVotifier refer to a new short string of characters. This
|
||||
string of characters is secret, and should never be exposed to anyone but you or
|
||||
server lists which support NuVotifier voting (check with the server list
|
||||
first). Tokens are most commonly used internally within a network for use when
|
||||
using the proxy vote forwarding method.
|
||||
|
||||
# Single Server Configuration (Bukkit / Spigot / Sponge)
|
||||
|
||||
You will want to set up your server using these instructions when you only have
|
||||
one Minecraft server (Bukkit / Sponge). These are the most trivial installation
|
||||
steps.
|
||||
|
||||
## Installing the plugin on a server
|
||||
|
||||
To install NuVotifier, you should get a copy from one of the following
|
||||
locations:
|
||||
|
||||
+ [Github](https://github.com/parallelblock/NuVotifier/releases) - Official
|
||||
release location
|
||||
+ [SpigotMC](https://www.spigotmc.org/resources/nuvotifier.13449/) - SpigotMC
|
||||
release mirror
|
||||
+ [repo.parallelblock.com](https://repo.parallelblock.com/repository/public-raw/nuvotifier/nuvotifier.jar) - ParallelBlock CI 'hot off the presses' releases
|
||||
|
||||
The NuVotifier jar should then be placed within the `plugins/` folder of your
|
||||
Minecraft server. When you then start your server, NuVotifier will generate
|
||||
the necessary configuration and initial keys for you.
|
||||
|
||||
## Configuring the plugin
|
||||
|
||||
When running in a single server configuration, NuVotifier's default
|
||||
configuration works well. The only value which you may need to change is the
|
||||
`port` configuration value - this specifies the port on which NuVotifier
|
||||
listens to new votes. This port cannot be any port you are already using. For
|
||||
example, if Minecraft is running on the default port (25565), you cannot set
|
||||
`port` also to 25565. Make sure port is a number from 1-65535.
|
||||
|
||||
You can then add your server to server lists using the port you configured while
|
||||
also supplying your public key. At this point, you should be able to send a test
|
||||
vote to your server!
|
||||
|
||||
# Multi-server / Network configuration
|
||||
|
||||
You will want to use these instructions when you have more than one server and
|
||||
are using a Minecraft proxy (Bungeecord / Lilypad)
|
||||
|
||||
## Single Bungeecord Networks
|
||||
|
||||
When running a single-bungeecord network, NuVotifier is able to operate as both
|
||||
a Bungeecord vote receiver (emitting events for Bungeecord based vote listeners)
|
||||
as well as a vote forwarder. NuVotifier's vote forwarding logic is also advanced
|
||||
enough to be used in a wide variety of ways.
|
||||
|
||||
### Installing the plugin on the servers
|
||||
|
||||
NuVotifier should be installed on each of the servers you wish to process votes
|
||||
on, as well as the Bungeecord server. The Bungeecord server will act as the
|
||||
public facing Votifier instance, where as the Minecraft servers will instead
|
||||
receive instructions from the Bungeecord Nuvotifier for forwarding votes.
|
||||
|
||||
Like, the single server installation, you can download NuVotifier at any one of
|
||||
the following locations:
|
||||
|
||||
+ [Github](https://github.com/parallelblock/NuVotifier/releases) - Official
|
||||
release location
|
||||
+ [SpigotMC](https://www.spigotmc.org/resources/nuvotifier.13449/) - SpigotMC
|
||||
release mirror
|
||||
+ [repo.parallelblock.com](https://repo.parallelblock.com/repository/public-raw/nuvotifier/nuvotifier.jar) - ParallelBlock CI 'hot off the presses' releases
|
||||
|
||||
The plugin should then be placed in the `plugins/` directory of the Bungeecord
|
||||
server as well as each of the Minecraft servers.
|
||||
|
||||
### Vote Forwarding
|
||||
|
||||
The following sections describe how vote forwarding works. Vote forwarding is an
|
||||
advanced configurable operation, and requires a lot of configuration to get
|
||||
correct.
|
||||
|
||||
### Plugin messaging based forwarding
|
||||
|
||||
This section describes how to set up plugin messaging forwarding. This will only
|
||||
work correctly if you have one Bungeecord server, however may also work with
|
||||
some exceptions in multi Bungeecord networks.
|
||||
|
||||
First off, we should configure each of the Minecraft servers. Open each of the
|
||||
servers' NuVotifier configurations. Each one should have `method` under the
|
||||
`forwarding` configuration section set to `pluginMessaging`. Under the
|
||||
`pluginMessaging` configuration section, `channel` is set to `NuVotifier`. This
|
||||
is a safe default (and is protected from injection by a hacked Minecraft
|
||||
client), but feel free to change it. If you choose to change it, make sure you
|
||||
change it everywhere all around your network!
|
||||
|
||||
Since we are receiving forwarded votes through plugin messaging and not through
|
||||
network ports, we should set `port` on each of the Minecraft servers to -1. This
|
||||
will disable NuVotifier's port, while still allowing it to receive forwarded
|
||||
plugin messaging votes. This will only work when using pluginMessaging!
|
||||
|
||||
Since we have all of that configured, we can now move to the Bungeecord
|
||||
NuVotifier configuration. It is once again located under `plugins/NuVotifier/`
|
||||
directory (`config.yml`). Like the single server, `port` should be set to an
|
||||
unbound port. The default `8192` works well, however you may have to change it
|
||||
depending on your hosting provider.
|
||||
|
||||
Since we are using pluginMessaging based forwarding, lets set `method` under
|
||||
`forwarding` to `pluginMessaging`. Make sure that `channel` is the same channel
|
||||
that you configured your servers to listen to - if you didn't change this value,
|
||||
then the default will work.
|
||||
|
||||
NuVotifier saves when it can't immediately forward in a user defined cache. In
|
||||
almost all installations this should be `file`, however, some installations may
|
||||
instead want `none` or `memory`. If you don't know, the `cache` should be set to
|
||||
it's default, `file`.
|
||||
|
||||
By default, NuVotifier will forward the vote to all of the connected Minecraft
|
||||
servers (all servers you can get to when typing /server). You can manually
|
||||
exclude some of these servers by first uncommenting `excludedServers`, then
|
||||
adding the server's name to the `excludedServers` list. After adding, NuVotifier
|
||||
will not send any votes to this server. If you only send the vote to the server
|
||||
the player joined, these servers are ignored.
|
||||
|
||||
NuVotifier can also selectively send the vote to only the server which the
|
||||
player has joined. This option can be turned on by setting
|
||||
`onlySendToJoinedServer` to true. When this option is on, the vote will attempt
|
||||
to go to the server which the player is currently on. If the player is currently
|
||||
not on a server (offline), then NuVotifier will then attempt to send the vote to
|
||||
the `joinedServerFallback`. If the vote cannot be sent to the
|
||||
`joinedServerFallback`, then the vote is saved in the cache until the vote can
|
||||
be sent to the `joinedServerFallback` server - note this will not send it to the
|
||||
next server the player joins! If you do not want a fallback server and would
|
||||
rather the vote be applied when the player next joins, set
|
||||
`joinedServerFallback` to `''`. This will cause NuVotifier to save the vote
|
||||
until the player joins again.
|
||||
|
||||
By default, votes are saved for 10 days in the cache. This time can be increased
|
||||
or decreased by changing the `voteTTL` field. This number can be as high or low
|
||||
as you want. It is measured in days within the cache.
|
||||
|
||||
### Proxy based vote forwarding
|
||||
|
||||
In more complex network situations, it may be required to instead use the proxy
|
||||
based vote forwarding. This configuration is less flexible and more difficult to
|
||||
set up properly - most people should use plugin messaging based forwarding.
|
||||
Proxy based forwarding does not support vote caching or advanced forwarding
|
||||
configurations. If you cannot use plugin messaging forwarding, then the
|
||||
following section will describe how to set up proxy based vote forwarding. If
|
||||
you still require advanced forwarding features or more advanced behaviors, it
|
||||
may be worthwhile to write your own Bungeecord plugin to perform the proper
|
||||
forwarding logic (you will know if you need to do this).
|
||||
|
||||
In proxy based vote forwarding, one of your Bungeecord servers acts as the head
|
||||
vote receiver. This server will be the one you configure the voting websites to
|
||||
vote to. You can set up multiple vote receivers and load balance between them,
|
||||
however this configuration has limited benefit and is probably more work than it
|
||||
is worth. Instead of communicating through plugin messaging like the plugin
|
||||
messaging forwarding strategy, proxy forwarding instead acts like a voting
|
||||
website to each of the backend servers and duplicates the vote it receives to
|
||||
each of the backend servers it is configured with.
|
||||
|
||||
First, we will set up all of the Minecraft servers before setting up the head
|
||||
vote receiver. For each of the servers, NuVotifier should be configured to bind
|
||||
to an unused port. This port must be valid. If you have multiple votifiers on
|
||||
the same IP/server, make sure that the port which you are using between
|
||||
NuVotifier instances are different. Remember where to find the port number as we
|
||||
will need it later to insert into the vote receiver Bungeecord NuVotifier
|
||||
configuration. In addition to the port number, you will also need the default
|
||||
token - this is the string auto generated by NuVotifier under the `default` key
|
||||
under `tokens`. Since we are using proxy forwarding, change
|
||||
`disable-v1-protocol` to true - we will only be using the new NuVotifier
|
||||
votifier protocol within our network (vote receiver -> minecraft server). Once
|
||||
all configured, that is all we need to do. The rest of the defaults will work.
|
||||
|
||||
Now we will configure the vote receiving NuVotifier Bungeecord server. The port
|
||||
should be configured as described many times above, being a unique port. Instead
|
||||
of setting `method` to `pluginMessaging` like above, we will set `method` to
|
||||
`proxy`. This will tell NuVotifier that we instead want to proxy the votes
|
||||
instead of forward them through plugin messaging channels.
|
||||
|
||||
The last step is to set up the proxy forwaring settings. Under the `proxy`
|
||||
section is a place to enter each server. Create a unique name for each of the
|
||||
server's you will be forwarding votes to. The default Bungeecord NuVotifier
|
||||
configuration already has two Minecraft servers filled in - Hub and Hub2. You
|
||||
should replace these with your own servers - the names of the servers do not
|
||||
matter. Under each of these servers, we need to configure `address`, `port`, and
|
||||
`token`. `address` is the IP address of the Bukkit/Sponge server. This may be
|
||||
`localhost` or `127.0.0.1` if the Minecraft server is on the same physical
|
||||
server as the vote receiving NuVotifier server. `port` is the port of the
|
||||
Bukkit/Sponge NuVotifier server. `token` is the default token of the
|
||||
Bukkit/Sponge NuVotifier server. This can be found within its configuration,
|
||||
usually under the `token` section. Once the servers are configured, NuVotifier
|
||||
will forward all votes it receives to the servers configured under the `proxy`
|
||||
section.
|
||||
|
||||
# Debugging NuVotifier
|
||||
|
||||
NuVotifier can be difficult to set up in some configurations. If you find you
|
||||
don't know what is wrong, you may want to try setting `debug` in each of the
|
||||
server's NuVotifier configurations to `true`. This will increase logging within
|
||||
your console.
|
||||
|
||||
# Default Configurations
|
||||
|
||||
If you find you have messed up NuVotifier beyond repair, you can grab a default
|
||||
configuration from the following locations (depending on the server it is
|
||||
installed on):
|
||||
|
||||
+ [Bukkit](https://github.com/parallelblock/NuVotifier/blob/master/bukkit/src/main/resources/bukkitConfig.yml)
|
||||
+ [Sponge](https://github.com/parallelblock/NuVotifier/blob/master/sponge/src/main/resources/com/vexsoftware/votifier/sponge/spongeConfig.yml)
|
||||
+ [Bungeecord](https://github.com/parallelblock/NuVotifier/blob/master/bungeecord/src/main/resources/bungeeConfig.yml)
|
||||
|
||||
You can also regenerate this configuration by deleting the old configuration and
|
||||
restarting the server.
|
||||
@@ -0,0 +1,151 @@
|
||||
/* This is free and unencumbered software released into the public domain */
|
||||
|
||||
import org.gradle.plugins.ide.eclipse.model.EclipseModel
|
||||
import java.nio.file.Files
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.OutputKeys
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
|
||||
val kotlinAttribute: Attribute<Boolean> by rootProject.extra
|
||||
|
||||
val kotlinPluginProjects = configurations
|
||||
.getByName("compileClasspath")
|
||||
.allDependencies
|
||||
.filterIsInstance<ProjectDependency>()
|
||||
.filter { it -> it.attributes.getAttribute(kotlinAttribute) != null }
|
||||
.map { it.path }
|
||||
kotlinPluginProjects.forEach { evaluationDependsOn(it) }
|
||||
|
||||
val ideLibs: Configuration by
|
||||
configurations.creating {
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
}
|
||||
|
||||
configure<EclipseModel> {
|
||||
project {
|
||||
name = "Template-OG-Plugin"
|
||||
}
|
||||
classpath {
|
||||
plusConfigurations.add(ideLibs)
|
||||
}
|
||||
}
|
||||
|
||||
val ideLibDir = layout.buildDirectory.dir("ide-libs")
|
||||
val hashRegex = Regex("-[0-9a-fA-F]{10}(?=\\.jar$)") // Black magic.
|
||||
|
||||
val copyTasks = mutableListOf<TaskProvider<Copy>>()
|
||||
|
||||
kotlinPluginProjects.forEach { path ->
|
||||
val sub = project(path)
|
||||
val shadowJarProv = sub.tasks.named("shadowJar")
|
||||
val copyTask =
|
||||
tasks.register<Copy>("ideCopy${sub.name.replaceFirstChar(Char::titlecase)}") {
|
||||
dependsOn(shadowJarProv)
|
||||
from(shadowJarProv)
|
||||
into(ideLibDir)
|
||||
rename { it.replace(hashRegex, "") } // Remove git commit hash from jarfile.
|
||||
}
|
||||
copyTasks += copyTask
|
||||
}
|
||||
|
||||
/* Ensure the jars exist before .classpath is generated */
|
||||
tasks.named("eclipseClasspath").configure { dependsOn(copyTasks) }
|
||||
|
||||
/* Supply those jars to the ideLibs configuration (after evaluation) */
|
||||
afterEvaluate { dependencies { add("ideLibs", fileTree(ideLibDir) { include("*.jar") }) } }
|
||||
|
||||
val injectIdeLibs =
|
||||
tasks.register("injectIdeLibs") {
|
||||
dependsOn("eclipse") // run after all eclipse files are generated
|
||||
doLast {
|
||||
val cpFile = file(".classpath")
|
||||
if (!cpFile.exists()) return@doLast
|
||||
|
||||
val jars = fileTree(ideLibDir.get()) { include("*.jar") }.files.sortedBy { it.name }
|
||||
if (jars.isEmpty()) return@doLast
|
||||
|
||||
// Parse DOM
|
||||
val dbf = DocumentBuilderFactory.newInstance()
|
||||
val doc = dbf.newDocumentBuilder().parse(cpFile)
|
||||
val root = doc.documentElement
|
||||
|
||||
// Helper to see if an entry already exists
|
||||
fun exists(path: String): Boolean =
|
||||
root.getElementsByTagName("classpathentry").let { list ->
|
||||
(0 until list.length).any { i ->
|
||||
val n = list.item(i)
|
||||
val kind = n.attributes?.getNamedItem("kind")?.nodeValue
|
||||
val p = n.attributes?.getNamedItem("path")?.nodeValue
|
||||
kind == "lib" && p == path
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any entry that points to ide-libs dir (folder or jars) to avoid dupes
|
||||
val toRemove = mutableListOf<org.w3c.dom.Node>()
|
||||
val list = root.getElementsByTagName("classpathentry")
|
||||
val dirPath = ideLibDir.get().asFile.absolutePath
|
||||
for (i in 0 until list.length) {
|
||||
val n = list.item(i)
|
||||
val kind = n.attributes?.getNamedItem("kind")?.nodeValue
|
||||
val p = n.attributes?.getNamedItem("path")?.nodeValue ?: ""
|
||||
if (kind == "lib" && (p == dirPath || p.startsWith("$dirPath/"))) {
|
||||
toRemove += n
|
||||
}
|
||||
}
|
||||
toRemove.forEach { root.removeChild(it) }
|
||||
|
||||
// Append our jar entries LAST
|
||||
jars.forEach { f ->
|
||||
val abs = f.absolutePath
|
||||
if (!exists(abs)) {
|
||||
val entry = doc.createElement("classpathentry")
|
||||
entry.setAttribute("kind", "lib")
|
||||
entry.setAttribute("path", abs)
|
||||
root.appendChild(entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Write back pretty
|
||||
val tf =
|
||||
TransformerFactory.newInstance().newTransformer().apply {
|
||||
setOutputProperty(OutputKeys.INDENT, "yes")
|
||||
setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1")
|
||||
setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no")
|
||||
setOutputProperty(OutputKeys.ENCODING, "UTF-8")
|
||||
}
|
||||
Files.newBufferedWriter(cpFile.toPath()).use { w -> tf.transform(DOMSource(doc), StreamResult(w)) }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("eclipse").configure { finalizedBy(injectIdeLibs) }
|
||||
|
||||
val eclipseModel = extensions.getByType<EclipseModel>()
|
||||
|
||||
fun Project.addResolvableEclipseConfigs() {
|
||||
val jarAttr =
|
||||
objects.named(org.gradle.api.attributes.LibraryElements::class, org.gradle.api.attributes.LibraryElements.JAR)
|
||||
val apiAttr = objects.named(org.gradle.api.attributes.Usage::class, org.gradle.api.attributes.Usage.JAVA_API)
|
||||
|
||||
val compileOnlyRes =
|
||||
configurations.create("eclipseCompileOnly") {
|
||||
extendsFrom(configurations.getByName("compileOnly"))
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr)
|
||||
attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr)
|
||||
}
|
||||
val compileOnlyApiRes =
|
||||
configurations.create("eclipseCompileOnlyApi") {
|
||||
extendsFrom(configurations.getByName("compileOnlyApi"))
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr)
|
||||
attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr)
|
||||
}
|
||||
eclipseModel.classpath.plusConfigurations.addAll(listOf(compileOnlyRes, compileOnlyApiRes))
|
||||
}
|
||||
|
||||
addResolvableEclipseConfigs()
|
||||
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4G
|
||||
group=net.trueog.votifier-og
|
||||
version=3.0.2
|
||||
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user