Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
3ac45554ac | ||
|
|
d27f6c5cea | ||
|
|
4d1f7016f6 | ||
|
|
2673fc2d4d | ||
|
|
60c787c7f2 | ||
|
|
713cf90c56 | ||
|
|
eba7ab9c86 | ||
|
|
d72977325c | ||
|
|
c3e9a5bac9 | ||
|
|
161617d636 | ||
|
|
cf95fd1f28 | ||
|
|
8f1bf5b4b9 | ||
|
|
ee9d3cae42 | ||
|
|
e4f910e576 | ||
|
|
77cf6bf457 | ||
|
|
8a72c4d9c7 | ||
|
|
39ae4ef860 | ||
|
|
d2d12a4a86 | ||
|
|
5644078997 | ||
|
|
bf5822282e | ||
|
|
55e883759f | ||
|
|
ba6a118058 | ||
|
|
93c0d53f2d | ||
|
|
8d4bdf69b3 | ||
|
|
700be0a04e | ||
|
|
bf7c78de46 | ||
|
|
c0ea57176c | ||
|
|
65bf833fa7 | ||
|
|
03beac9ae0 | ||
|
|
4277dfd74d | ||
|
|
28b4d8dcd0 | ||
|
|
733204b6b9 | ||
|
|
52956ce210 | ||
|
|
2de64da511 | ||
|
|
0ea6867d91 | ||
|
|
190bfaa4a9 | ||
|
|
b43dda37b2 | ||
|
|
4dfb157166 | ||
|
|
d8c2e072b1 | ||
|
|
359b616a41 | ||
|
|
9342f75317 | ||
|
|
f17bb4e414 | ||
|
|
5f347e538b | ||
|
|
b4fb8c69a9 | ||
|
|
95ebcf22a8 | ||
|
|
302da3c907 | ||
|
|
f8019debd0 | ||
|
|
ab317e76b3 | ||
|
|
15556be265 | ||
|
|
26958e35fe | ||
|
|
e905881238 | ||
|
|
fde7a941b6 | ||
|
|
9d24deabe9 | ||
|
|
c2f331e7ec | ||
|
|
705bddd062 |
No files matched your search
@@ -0,0 +1 @@
|
||||
custom: https://www.paypal.me/ibj
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for submitting an issue for NuVotifier. We appreciate
|
||||
any and all bug reports.
|
||||
|
||||
Before you continue, please ensure you are reporting
|
||||
a real bug and are not suffering from a configuration issue
|
||||
or a plugin problem. Once NuVotifier has received and
|
||||
called your vote listeners, it's not our issue any more.
|
||||
|
||||
If you are having difficulty setting up NuVotifier to receive
|
||||
votes, you are in the wrong place. Please view our wiki
|
||||
at https://github.com/NuVotifier/NuVotifier/wiki for a
|
||||
comprehensive set of instructions for configuring and
|
||||
diagnosing common problems with NuVotifier.
|
||||
|
||||
If you are certain that there is a problem with
|
||||
NuVotifier, please continue filling the form below.
|
||||
Make sure to follow these instructions:
|
||||
|
||||
1. Tell the exact NuVotifier version you're using.
|
||||
If you just say "latest" your issue will get closed.
|
||||
2. Please include all relevant logs and configuration
|
||||
files. Make sure to redact any tokens in the config
|
||||
file.
|
||||
3. Don't write anything between these arrows.
|
||||
We won't see it otherwise.
|
||||
4. Before you create your issue, delete this block of
|
||||
text and the code block at the very bottom. -->
|
||||
|
||||
### Information
|
||||
|
||||
**Platform on which you run NuVotifier**: <!-- Either Bukkit, Sponge, BungeeCord, or Velocity. -->
|
||||
|
||||
**Version of NuVotifier**:
|
||||
|
||||
```
|
||||
<!--
|
||||
Getting this depends on your platform:
|
||||
|
||||
Bukkit: Use /version Votifier
|
||||
|
||||
Sponge: Use /sponge plugins and find NuVotifier
|
||||
|
||||
BungeeCord: No such command exists.
|
||||
|
||||
Velocity: Use /velocity plugins from in-game and hover over "nuvotifier"
|
||||
-->
|
||||
```
|
||||
|
||||
**Relevant log**: <!-- Paste the log on https://gist.github.com/, replace this with link -->
|
||||
### Details
|
||||
|
||||
**Description**
|
||||
<!-- Replace this with a brief summary of the bug. -->
|
||||
|
||||
**Steps to reproduce**
|
||||
<!-- Replace this with exactly what you did to cause the bug. -->
|
||||
|
||||
**Expected behavior**
|
||||
<!-- Replace this with what you expected to happen. -->
|
||||
|
||||
```
|
||||
Please make sure to delete this line of text and the three ``` characters surrounding it, or your issue will be closed.
|
||||
```
|
||||
+7
-2
@@ -11,13 +11,14 @@
|
||||
/build.xml
|
||||
|
||||
# maven
|
||||
/target
|
||||
/*/target/
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
# vim
|
||||
.*.sw[a-p]
|
||||
|
||||
# various other potential build files
|
||||
/build
|
||||
build/
|
||||
/bin
|
||||
/dist
|
||||
/manifest.mf
|
||||
@@ -42,3 +43,7 @@ debug.log
|
||||
# Doxygen
|
||||
/docs/html
|
||||
debug.txt
|
||||
|
||||
# gradle stuff
|
||||
.gradle/
|
||||
.gradletasknamecache
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
sudo: false
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: j8y4qeDOXAYjmf6WU6EbpUA6AKTz26yJ4zNb6oAuB4h9Y3PpBRjFjCIBL6jJQnMxZMLjdgk72/3uqS7+//jJ8/iiY/OJhO2L+2us8TJDcQhH5EI1USe/lcZKf60iJjmd9Q9pjh1eHNYhxr6kyNLnpa6fw5b/hIBT6CvNZAJV1qbhRpHwtzbgdlMFaHGnpX192vJNw7E3uuJuSwue5XNK2ybDp81oTrM0Q16JCq/UsQhGq4Ba3JffOhdBhpfg4EI2jJfe3vN8CFqojCYKNSp7eL6DPFPaV4x8Ly8cRIOZ5Zb60m5SyWAcagPKJuUq3ZOTHfeVTjkI9ymZMHe1pla2Mq6eitIBwZIGnonjyMxcTdkR9EJgq9jo+1PzdStR/NlGLakBiwPzk8Ov0DZ+iOwpYUkISUkMU0UgKWwoFoRu18CHtp+ALEsoYZhBQGlcShq+4EGv9VMNBYk9vN3KXNRMVGKqXd6O3T6shvSQgoUn/3mvksOTAuhS5J4U3woHecDrXwl/RfP2aqiHhX/S7p7yY4hVcHu8kiSYpNQMbay+rNd6HxFcRe/U9HOUt6kW8/yo1ROyCxs3ze+N6LtGDHspgzkFaay6I6wMHYNDnzzRP4MC3Zc73zxViXol6dGBk4Tawsm16VaI6b1uyWLBGKvuU23azkmMTHUmGFXl2/BTaYw=
|
||||
file: universal/target/nuvotifier-universal-2.3.3.jar
|
||||
on:
|
||||
tags: true
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker { image 'openjdk:12-jdk' }
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh './gradlew build --no-daemon'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh './gradlew test --no-daemon'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Publish') {
|
||||
when {
|
||||
branch "master"
|
||||
}
|
||||
environment {
|
||||
SNAPSHOT_REPO = credentials('ibj-nexus-snapshot-repo')
|
||||
RELEASE_REPO = credentials('ibj-nexus-release-repo')
|
||||
RAW_UPLOAD_PATH = credentials('ibj-nexus-raw-path')
|
||||
REPO = credentials('ibj-nexus-access')
|
||||
PUBLISH = 'true'
|
||||
LATEST_BUILD = 'true'
|
||||
}
|
||||
steps {
|
||||
sh './gradlew publishVersionedPublicationToIbjRepository'
|
||||
sh './gradlew publishLatestToIbjBlockRaw'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
# NuVotifier
|
||||
|
||||
NuVotifier is a Bukkit plugin whose purpose is to be notified (aka *votified*) when a vote is made on a Minecraft server top list for the server. NuVotifier creates a *lightweight* server that waits for connections by Minecraft server lists and uses a simple protocol to get the required information. NuVotifier is *secure*, and makes sure that all vote notifications are delivered by authentic top lists.
|
||||
|
||||
[](https://travis-ci.org/NuVotifier/NuVotifier)
|
||||
|
||||
## 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. 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.
|
||||
|
||||
### 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.
|
||||
|
||||
## Configuring NuVotifier
|
||||
|
||||
NuVotifier configures itself the first time it is run. If you have an existing Votifier configuration, NuVotifier will copy it over and use it.
|
||||
|
||||
If you want to customize NuVotifier, simply the edit `./plugins/(Nu)Votifier/config.yml` file.
|
||||
|
||||
An in-depth configuration guide is located in the [usage](https://github.com/NuVotifier/NuVotifier/blob/master/usage.md) document.
|
||||
|
||||
### Tokens
|
||||
|
||||
In `config.yml`, you will notice a `tokens` section. NuVotifier has a new protocol version that requires tokens. The `tokens` section can be used to easily grant or revoke access to a server list based on the keys it uses.
|
||||
|
||||
### RSA Keys
|
||||
|
||||
RSA keys for use with the old Votifier protocol are available under the `plugins/(Nu)Votifier/rsa` directory.
|
||||
|
||||
### Vote Forwarding
|
||||
|
||||
Starting from NuVotifier 2.2, there is a feature to forward votes from NuVotifier running on BungeeCord to your Bukkit servers without complicated setup.
|
||||
|
||||
You must enable the feature on both your Bukkit and BungeeCord servers. This feature is still in beta but is stable.
|
||||
|
||||
## Building NuVotifier
|
||||
|
||||
NuVotifier can be built by running `mvn clean package` from within the root directory of NuVotifier. A universal Bukkit/Spigot & BungeeCord plugin will be built to `universal/target/nuvotifier-universal-{version}.jar`
|
||||
|
||||
## 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) and [PHP](https://github.com/NuVotifier/votifier2-php). 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.
|
||||
@@ -0,0 +1,25 @@
|
||||
# NuVotifier
|
||||
|
||||
NuVotifier is a secure alternative to using the original Votifier project.
|
||||
NuVotifier will work in place of Votifier - any vote listener that supports
|
||||
Votifier will also support NuVotifier.
|
||||
|
||||
NuVotifier supports many different platforms using the same universal JAR:
|
||||
|
||||
+ Bukkit / Spigot / Paper >=1.7.10
|
||||
+ Sponge 7
|
||||
+ BungeeCord / Waterfall
|
||||
+ Velocity
|
||||
|
||||
NuVotifier also adds forwarding and listener test commands not present in the
|
||||
original version.
|
||||
|
||||
[Setup Guide](https://github.com/NuVotifier/NuVotifier/wiki/Setup-Guide)
|
||||
|
||||
[Troubleshooting Guide](https://github.com/NuVotifier/NuVotifier/wiki/Troubleshooting-Guide)
|
||||
|
||||
[Developer Information](https://github.com/NuVotifier/NuVotifier/wiki/Developer-Documentation)
|
||||
|
||||
# License
|
||||
|
||||
NuVotifier is GNU GPLv3 licensed. This project's license can be viewed [here](LICENSE).
|
||||
@@ -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!
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies {
|
||||
implementation 'com.google.code.gson:gson:2.8.0'
|
||||
}
|
||||
+59
-16
@@ -18,7 +18,11 @@
|
||||
|
||||
package com.vexsoftware.votifier.model;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* A model for a vote.
|
||||
@@ -47,6 +51,8 @@ public class Vote {
|
||||
*/
|
||||
private String timeStamp;
|
||||
|
||||
private byte[] additionalData;
|
||||
|
||||
@Deprecated
|
||||
public Vote() {
|
||||
}
|
||||
@@ -56,24 +62,50 @@ public class Vote {
|
||||
this.username = username;
|
||||
this.address = address;
|
||||
this.timeStamp = timeStamp;
|
||||
this.additionalData = null;
|
||||
}
|
||||
|
||||
private static String getTimestamp(JSONObject object) {
|
||||
public Vote(String serviceName, String username, String address, String timeStamp, byte[] additionalData) {
|
||||
this.serviceName = serviceName;
|
||||
this.username = username;
|
||||
this.address = address;
|
||||
this.timeStamp = timeStamp;
|
||||
this.additionalData = additionalData == null ? null : additionalData.clone();
|
||||
}
|
||||
|
||||
public Vote(Vote vote) {
|
||||
this(vote.getServiceName(), vote.getUsername(), vote.getAddress(), vote.getTimeStamp(),
|
||||
vote.getAdditionalData() == null ? null : vote.getAdditionalData().clone());
|
||||
}
|
||||
|
||||
private static String getTimestamp(JsonElement object) {
|
||||
try {
|
||||
return Long.toString(object.getLong("timestamp"));
|
||||
return Long.toString(object.getAsLong());
|
||||
} catch (Exception e) {
|
||||
return object.getString("timestamp");
|
||||
return object.getAsString();
|
||||
}
|
||||
}
|
||||
|
||||
public Vote(JSONObject jsonObject) {
|
||||
this(jsonObject.getString("serviceName"), jsonObject.getString("username"), jsonObject.getString("address"), getTimestamp(jsonObject));
|
||||
public Vote(JsonObject jsonObject) {
|
||||
this(jsonObject.get("serviceName").getAsString(),
|
||||
jsonObject.get("username").getAsString(),
|
||||
jsonObject.get("address").getAsString(),
|
||||
getTimestamp(jsonObject.get("timestamp")));
|
||||
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 + ")";
|
||||
+ " address:" + address + " timeStamp:" + timeStamp
|
||||
+ " additionalData:" + data + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,27 +184,38 @@ public class Vote {
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
public JSONObject serialize() {
|
||||
JSONObject ret = new JSONObject();
|
||||
ret.put("serviceName", serviceName);
|
||||
ret.put("username", username);
|
||||
ret.put("address", address);
|
||||
ret.put("timestamp", timeStamp);
|
||||
/**
|
||||
* Returns additional data sent with the vote, if it exists.
|
||||
*
|
||||
* @return Additional data sent with the vote
|
||||
*/
|
||||
public byte[] getAdditionalData() {
|
||||
return additionalData == null ? null : additionalData.clone();
|
||||
}
|
||||
|
||||
public JsonObject serialize() {
|
||||
JsonObject ret = new JsonObject();
|
||||
ret.addProperty("serviceName", serviceName);
|
||||
ret.addProperty("username", username);
|
||||
ret.addProperty("address", address);
|
||||
ret.addProperty("timestamp", timeStamp);
|
||||
if (additionalData != null)
|
||||
ret.addProperty("additionalData", Base64.getEncoder().encodeToString(additionalData));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!(o instanceof Vote)) return false;
|
||||
|
||||
Vote vote = (Vote) o;
|
||||
|
||||
if (!serviceName.equals(vote.serviceName)) return false;
|
||||
if (!username.equals(vote.username)) return false;
|
||||
if (!address.equals(vote.address)) return false;
|
||||
return timeStamp.equals(vote.timeStamp);
|
||||
|
||||
if (!timeStamp.equals(vote.timeStamp)) return false;
|
||||
return Arrays.equals(additionalData, vote.additionalData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.vexsoftware.votifier.model;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class VoteTest {
|
||||
@Test
|
||||
public void testVoteConstructFromJsonObject() {
|
||||
|
||||
String serviceName = "serviceNameA";
|
||||
String username = "usernameA";
|
||||
String address = "home";
|
||||
String timestamp = "1234";
|
||||
|
||||
JsonObject o = new JsonObject();
|
||||
o.addProperty("serviceName", serviceName);
|
||||
o.addProperty("username", username);
|
||||
o.addProperty("address", address);
|
||||
o.addProperty("timestamp", timestamp);
|
||||
|
||||
Vote v = new Vote(o);
|
||||
|
||||
assertEquals(serviceName, v.getServiceName());
|
||||
assertEquals(username, v.getUsername());
|
||||
assertEquals(address, v.getAddress());
|
||||
assertEquals(timestamp, v.getTimeStamp());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVoteConstructFromJsonObjectLongTS() {
|
||||
|
||||
String serviceName = "serviceNameA";
|
||||
String username = "usernameA";
|
||||
String address = "home";
|
||||
|
||||
JsonObject o = new JsonObject();
|
||||
o.addProperty("serviceName", serviceName);
|
||||
o.addProperty("username", username);
|
||||
o.addProperty("address", address);
|
||||
o.addProperty("timestamp", 1234);
|
||||
|
||||
Vote v = new Vote(o);
|
||||
|
||||
assertEquals(serviceName, v.getServiceName());
|
||||
assertEquals(username, v.getUsername());
|
||||
assertEquals(address, v.getAddress());
|
||||
assertEquals("1234", v.getTimeStamp());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVoteSerialize() {
|
||||
Vote v = new Vote("service", "username", "address", "1234");
|
||||
JsonObject o = v.serialize();
|
||||
|
||||
assertEquals(4, o.size());
|
||||
assertEquals("service", o.get("serviceName").getAsString());
|
||||
assertEquals("username", o.get("username").getAsString());
|
||||
assertEquals("address", o.get("address").getAsString());
|
||||
assertEquals("1234", o.get("timestamp").getAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVoteEquals() {
|
||||
Vote v1 = new Vote("service", "username", "address", "1234");
|
||||
assertEquals(v1, v1);
|
||||
assertEquals(v1.hashCode(), new Vote("service", "username", "address", "1234").hashCode());
|
||||
assertEquals(v1, new Vote("service", "username", "address", "1234"));
|
||||
assertNotEquals(v1, new Object());
|
||||
assertNotEquals(v1, null);
|
||||
assertNotEquals(v1, new Vote("service2", "username", "address", "1234"));
|
||||
assertNotEquals(v1, new Vote("service", "username1", "address", "1234"));
|
||||
assertNotEquals(v1, new Vote("service", "username", "address1", "1234"));
|
||||
assertNotEquals(v1, new Vote("service", "username", "address", "12345"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
id "com.github.spotbugs" version "4.2.4" apply false
|
||||
id "net.kyori.blossom" version "1.1.0" apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group 'com.vexsoftware'
|
||||
version '2.7.2'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: "com.github.spotbugs"
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://hub.spigotmc.org/nexus/content/repositories/snapshots"
|
||||
}
|
||||
maven {
|
||||
url "https://oss.sonatype.org/content/repositories/snapshots"
|
||||
}
|
||||
maven {
|
||||
url "https://repo.spongepowered.org/maven"
|
||||
}
|
||||
maven {
|
||||
url "https://repo.velocitypowered.com/snapshots/"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.2"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.4.2"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params:5.4.2"
|
||||
testImplementation "org.mockito:mockito-core:2.+"
|
||||
}
|
||||
|
||||
processResources {
|
||||
filter(ReplaceTokens, tokens: ["app.version": this.project.version])
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
tasks.withType(com.github.spotbugs.snom.SpotBugsTask) {
|
||||
reports {
|
||||
html {
|
||||
enabled = true
|
||||
destination = file("$buildDir/reports/spotbugs/main/spotbugs.html")
|
||||
stylesheet = 'fancy-hist.xsl'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
dependencies {
|
||||
compileOnly group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
|
||||
implementation project(":nuvotifier-api")
|
||||
implementation project(":nuvotifier-common")
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-bukkit</artifactId>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>maven-replacer-plugin</artifactId>
|
||||
<version>1.3.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>replace</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<file>target/classes/plugin.yml</file>
|
||||
<replacements>
|
||||
<replacement>
|
||||
<token>maven-version-number</token>
|
||||
<value>${project.version}</value>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-common</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>bukkit</artifactId>
|
||||
<version>1.7.2-R0.1</version>
|
||||
<type>jar</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class BukkitScheduler implements VotifierScheduler {
|
||||
private final NuVotifierBukkit plugin;
|
||||
|
||||
public BukkitScheduler(NuVotifierBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private int toTicks(int time, TimeUnit unit) {
|
||||
return (int) (unit.toMillis(time) / 50);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTask(plugin, runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskLater(plugin, runnable, toTicks(delay, unit)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, toTicks(delay, unit)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, toTicks(delay, unit), toTicks(repeat, unit)));
|
||||
}
|
||||
|
||||
private static class BukkitTaskWrapper implements ScheduledVotifierTask {
|
||||
private final BukkitTask task;
|
||||
|
||||
private BukkitTaskWrapper(BukkitTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,39 +18,35 @@
|
||||
|
||||
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.support.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSink;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.model.VotifierEvent;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.util.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;
|
||||
@@ -66,24 +62,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 +82,20 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
private Map<String, Key> tokens = new HashMap<>();
|
||||
|
||||
private ForwardingVoteSink forwardingMethod;
|
||||
private VotifierScheduler scheduler;
|
||||
private LoggingAdapter pluginLogger;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
NuVotifierBukkit.instance = this;
|
||||
|
||||
// Set the plugin version.
|
||||
version = getDescription().getVersion();
|
||||
|
||||
private boolean loadAndBind() {
|
||||
scheduler = new BukkitScheduler(this);
|
||||
pluginLogger = new JavaUtilLogger(getLogger());
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
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
|
||||
@@ -137,13 +116,15 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("Configuring Votifier for the first time...");
|
||||
|
||||
// Initialize the configuration file.
|
||||
config.createNewFile();
|
||||
if (!config.createNewFile()) {
|
||||
throw new IOException("Unable to create the config file at " + config);
|
||||
}
|
||||
|
||||
// Load and manually replace variables in the configuration.
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
|
||||
String cfgStr = new String(IOUtil.readAllBytes(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
|
||||
Files.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 +142,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,7 +158,9 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
*/
|
||||
try {
|
||||
if (!rsaDirectory.exists()) {
|
||||
rsaDirectory.mkdir();
|
||||
if (!rsaDirectory.mkdir()) {
|
||||
throw new RuntimeException("Unable to create the RSA key folder " + rsaDirectory);
|
||||
}
|
||||
keyPair = RSAKeygen.generate(2048);
|
||||
RSAIO.save(rsaDirectory, keyPair);
|
||||
} else {
|
||||
@@ -184,51 +169,53 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error reading configuration file or RSA tokens", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
debug = cfg.getBoolean("debug", false);
|
||||
// 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);
|
||||
}
|
||||
|
||||
boolean setUpPort = cfg.getBoolean("enableExternal", true); //Always default to running the external port
|
||||
// Load Votifier tokens.
|
||||
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
|
||||
|
||||
if (setUpPort) {
|
||||
// Load Votifier tokens.
|
||||
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
|
||||
|
||||
if (tokenSection != null) {
|
||||
Map<String, Object> websites = tokenSection.getValues(false);
|
||||
for (Map.Entry<String, Object> website : websites.entrySet()) {
|
||||
tokens.put(website.getKey(), KeyCreator.createKeyFrom(website.getValue().toString()));
|
||||
getLogger().info("Loaded token for website: " + website.getKey());
|
||||
}
|
||||
} else {
|
||||
String token = TokenUtil.newToken();
|
||||
tokenSection = cfg.createSection("tokens");
|
||||
tokenSection.set("default", token);
|
||||
tokens.put("default", KeyCreator.createKeyFrom(token));
|
||||
try {
|
||||
cfg.save(config);
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error generating Votifier token", e);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
|
||||
getLogger().info("Your default Votifier token is " + token + ".");
|
||||
getLogger().info("You will need to provide this token when you submit your server to a voting");
|
||||
getLogger().info("list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
if (tokenSection != null) {
|
||||
Map<String, Object> websites = tokenSection.getValues(false);
|
||||
for (Map.Entry<String, Object> website : websites.entrySet()) {
|
||||
tokens.put(website.getKey(), KeyCreator.createKeyFrom(website.getValue().toString()));
|
||||
getLogger().info("Loaded token for website: " + website.getKey());
|
||||
}
|
||||
} else {
|
||||
String token = TokenUtil.newToken();
|
||||
tokenSection = cfg.createSection("tokens");
|
||||
tokenSection.set("default", token);
|
||||
tokens.put("default", KeyCreator.createKeyFrom(token));
|
||||
try {
|
||||
cfg.save(config);
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error generating Votifier token", e);
|
||||
return false;
|
||||
}
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
|
||||
getLogger().info("Your default Votifier token is " + token + ".");
|
||||
getLogger().info("You will need to provide this token when you submit your server to a voting");
|
||||
getLogger().info("list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
// Initialize the receiver.
|
||||
final String host = cfg.getString("host", hostAddr);
|
||||
final int port = cfg.getInt("port", 8192);
|
||||
if (debug)
|
||||
getLogger().info("DEBUG mode enabled!");
|
||||
// Initialize the receiver.
|
||||
final String host = cfg.getString("host", hostAddr);
|
||||
final int port = cfg.getInt("port", 8192);
|
||||
if (!debug)
|
||||
getLogger().info("QUIET mode enabled!");
|
||||
|
||||
if (port >= 0) {
|
||||
final boolean disablev1 = cfg.getBoolean("disable-v1-protocol");
|
||||
if (disablev1) {
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
@@ -237,39 +224,14 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
serverGroup = new NioEventLoopGroup(1);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(NuVotifierBukkit.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(NuVotifierBukkit.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
this.bootstrap = new VotifierServerBootstrap(host, port, this, disablev1);
|
||||
this.bootstrap.start(error -> {});
|
||||
} else {
|
||||
getLogger().info("You have enableExternal set to false in your config.yml. NuVotifier will NOT listen to votes coming in from an external voting list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Your Votifier port is less than 0, so we assume you do NOT want to start the");
|
||||
getLogger().info("votifier port server! Votifier will not listen for votes over any port, and");
|
||||
getLogger().info("will only listen for pluginMessaging forwarded votes!");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
ConfigurationSection forwardingConfig = cfg.getConfigurationSection("forwarding");
|
||||
@@ -289,42 +251,72 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().severe("No vote forwarding method '" + method + "' known. Defaulting to noop implementation.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void halt() {
|
||||
// Shut down the network handlers.
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
forwardingMethod = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
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() {
|
||||
@@ -342,28 +334,24 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(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 -> " + vote);
|
||||
} else {
|
||||
getLogger().info("Got a protocol v2 vote record -> " + 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));
|
||||
}
|
||||
});
|
||||
Bukkit.getScheduler().runTask(this, () -> fireVotifierEvent(vote));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, Throwable throwable) {
|
||||
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
|
||||
if (debug) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
|
||||
if (alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + remoteAddress, throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + remoteAddress, throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + remoteAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,11 +360,15 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
if (debug) {
|
||||
getLogger().info("Got a forwarded vote -> " + v);
|
||||
}
|
||||
Bukkit.getScheduler().runTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bukkit.getPluginManager().callEvent(new VotifierEvent(v));
|
||||
}
|
||||
});
|
||||
Bukkit.getScheduler().runTask(this, () -> fireVotifierEvent(v));
|
||||
}
|
||||
|
||||
private void fireVotifierEvent(Vote vote) {
|
||||
if (VotifierEvent.getHandlerList().getRegisteredListeners().length == 0) {
|
||||
getLogger().log(Level.SEVERE, "A vote was received, but you don't have any listeners available to listen for it.");
|
||||
getLogger().log(Level.SEVERE, "See https://github.com/NuVotifier/NuVotifier/wiki/Setup-Guide#vote-listeners for");
|
||||
getLogger().log(Level.SEVERE, "a list of listeners you can configure.");
|
||||
}
|
||||
Bukkit.getPluginManager().callEvent(new VotifierEvent(vote));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.vexsoftware.votifier.cmd;
|
||||
|
||||
import com.vexsoftware.votifier.NuVotifierBukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
public class NVReloadCmd implements CommandExecutor {
|
||||
|
||||
private final NuVotifierBukkit plugin;
|
||||
|
||||
public NVReloadCmd(NuVotifierBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender.hasPermission("nuvotifier.reload")) {
|
||||
sender.sendMessage(ChatColor.GRAY + "Reloading NuVotifier...");
|
||||
if (plugin.reload()) {
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "NuVotifier has been reloaded!");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Looks like there was a problem reloading NuVotifier, check the console!");
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You do not have permission to do this!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.vexsoftware.votifier.cmd;
|
||||
|
||||
import com.vexsoftware.votifier.NuVotifierBukkit;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.ArgsToVote;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
public class TestVoteCmd implements CommandExecutor {
|
||||
|
||||
private final NuVotifierBukkit plugin;
|
||||
|
||||
public TestVoteCmd(NuVotifierBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender.hasPermission("nuvotifier.testvote")) {
|
||||
Vote v;
|
||||
try {
|
||||
v = ArgsToVote.parse(args);
|
||||
} catch (IllegalArgumentException e) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Error while parsing arguments to create test vote: " + e.getMessage());
|
||||
sender.sendMessage(ChatColor.GRAY + "Usage hint: /testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]");
|
||||
return true;
|
||||
}
|
||||
|
||||
plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
|
||||
sender.sendMessage(ChatColor.GREEN + "Test vote executed: " + v.toString());
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You do not have permission to do this!");
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
+6
-11
@@ -1,33 +1,31 @@
|
||||
package com.vexsoftware.votifier.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.NuVotifierBukkit;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSink;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class BukkitPluginMessagingForwardingSink implements ForwardingVoteSink, PluginMessageListener {
|
||||
public class BukkitPluginMessagingForwardingSink extends AbstractPluginMessagingForwardingSink implements PluginMessageListener {
|
||||
|
||||
public BukkitPluginMessagingForwardingSink(Plugin p, String channel, ForwardedVoteListener listener) {
|
||||
super(listener);
|
||||
Validate.notNull(channel, "Channel cannot be null.");
|
||||
this.channel = channel;
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(p, channel, this);
|
||||
this.listener = listener;
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
private final Plugin p;
|
||||
private final String channel;
|
||||
private final ForwardedVoteListener listener;
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
@@ -37,12 +35,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 = new JSONObject(message);
|
||||
Vote v = new Vote(jsonObject);
|
||||
listener.onForward(v);
|
||||
this.handlePluginMessage(bytes);
|
||||
} catch (Exception e) {
|
||||
NuVotifierBukkit.getInstance().getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
|
||||
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,14 +5,6 @@ host: %ip%
|
||||
# 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 value to false will turn off the votifier port listening for external votes. This is beneficial if the server
|
||||
# is only listening for votes coming from NuVotifier running on your BungeeCord.
|
||||
enableExternal: true
|
||||
|
||||
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
|
||||
# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are
|
||||
# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.
|
||||
@@ -30,4 +22,4 @@ forwarding:
|
||||
# - pluginMessaging - Sets up plugin messaging
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
channel: nuvotifier:votes
|
||||
@@ -1,5 +1,26 @@
|
||||
name: Votifier
|
||||
main: com.vexsoftware.votifier.NuVotifierBukkit
|
||||
version: maven-version-number
|
||||
version: @app.version@
|
||||
description: A plugin that gets notified when votes are made for the server on toplists.
|
||||
authors: [blakeman8192, Kramer, tuxed]
|
||||
authors: [Ichbinjoe, blakeman8192, Kramer, tuxed]
|
||||
api-version: 1.13
|
||||
|
||||
commands:
|
||||
nvreload:
|
||||
description: Reloads the NuVotifier configuration
|
||||
permission: nuvotifier.reload
|
||||
permission-message: "You do not have permission to run this command."
|
||||
usage: "/nvreload"
|
||||
testvote:
|
||||
description: Sends a test vote to the server
|
||||
permission: nuvotifier.testvote
|
||||
permission-message: "You do not have permission to run this command."
|
||||
usage: "/testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]"
|
||||
|
||||
permissions:
|
||||
nuvotifier.reload:
|
||||
description: Allows you to reload the NuVotifier plugin
|
||||
default: op
|
||||
nuvotifier.testvote:
|
||||
description: Allows you to send a test vote
|
||||
default: op
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencies {
|
||||
compileOnly group: "net.md-5", name: "bungeecord-api", version: "1.8-SNAPSHOT"
|
||||
implementation project(":nuvotifier-api")
|
||||
implementation project(":nuvotifier-common")
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-bungeecord</artifactId>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>maven-replacer-plugin</artifactId>
|
||||
<version>1.3.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>replace</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<file>target/classes/bungee.yml</file>
|
||||
<replacements>
|
||||
<replacement>
|
||||
<token>maven-version-number</token>
|
||||
<value>${project.version}</value>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-common</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class BungeeBackendServer implements BackendServer {
|
||||
private final ServerInfo info;
|
||||
|
||||
public BungeeBackendServer(ServerInfo info) {
|
||||
this.info = Objects.requireNonNull(info, "info");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return info.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPluginMessage(String channel, byte[] data) {
|
||||
return info.sendData(channel, data, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return info.getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class BungeeScheduler implements VotifierScheduler {
|
||||
private final NuVotifier plugin;
|
||||
|
||||
public BungeeScheduler(NuVotifier plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return onPool(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().runAsync(plugin, runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return delayedOnPool(runnable, delay, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().schedule(plugin, runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().schedule(plugin, runnable, delay, repeat, unit));
|
||||
}
|
||||
|
||||
private static class BungeeTaskWrapper implements ScheduledVotifierTask {
|
||||
private final ScheduledTask task;
|
||||
|
||||
private BungeeTaskWrapper(ScheduledTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
this.task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,61 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.bungee.cmd.NVReloadCmd;
|
||||
import com.vexsoftware.votifier.bungee.cmd.TestVoteCmd;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
|
||||
import com.vexsoftware.votifier.bungee.events.VotifierEvent;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.OnlineForwardPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.PluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.MemoryVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.MemoryVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.util.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 net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlugin {
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
@@ -86,15 +77,19 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
*/
|
||||
private ForwardingVoteSource forwardingMethod;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
private VotifierScheduler scheduler;
|
||||
private LoggingAdapter pluginLogger;
|
||||
|
||||
private void loadAndBind() {
|
||||
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");
|
||||
File rsaDirectory = new File(getDataFolder() + "/rsa");
|
||||
File config = new File(getDataFolder() , "config.yml");
|
||||
File rsaDirectory = new File(getDataFolder() , "rsa");
|
||||
Configuration configuration;
|
||||
|
||||
if (!config.exists()) {
|
||||
@@ -103,12 +98,14 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
getLogger().info("Configuring Votifier for the first time...");
|
||||
|
||||
// Initialize the configuration file.
|
||||
config.createNewFile();
|
||||
if (!config.createNewFile()) {
|
||||
throw new IOException("Unable to create the config file at " + config);
|
||||
}
|
||||
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
|
||||
String cfgStr = new String(IOUtil.readAllBytes(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token);
|
||||
Files.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
|
||||
@@ -146,7 +143,9 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
*/
|
||||
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 {
|
||||
@@ -166,7 +165,7 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
} else {
|
||||
String token = TokenUtil.newToken();
|
||||
configuration.set("tokens", ImmutableMap.of("default", token));
|
||||
configuration.set("tokens", Collections.singletonMap("default", token));
|
||||
tokens.put("default", KeyCreator.createKeyFrom(token));
|
||||
try {
|
||||
ConfigurationProvider.getProvider(YamlConfiguration.class).save(configuration, config);
|
||||
@@ -184,9 +183,14 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
// 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!");
|
||||
|
||||
if (configuration.get("quiet") != null)
|
||||
debug = !configuration.getBoolean("quiet");
|
||||
else
|
||||
debug = configuration.getBoolean("debug", true);
|
||||
|
||||
if (!debug)
|
||||
getLogger().info("QUIET mode enabled!");
|
||||
|
||||
final boolean disablev1 = configuration.getBoolean("disable-v1-protocol");
|
||||
if (disablev1) {
|
||||
@@ -197,41 +201,9 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
|
||||
// Must set up server asynchronously due to BungeeCord goofiness.
|
||||
FutureTask<?> initTask = new FutureTask<>(Executors.callable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
serverGroup = new NioEventLoopGroup(2);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(NuVotifier.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(NuVotifier.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
FutureTask<?> initTask = new FutureTask<>(Executors.callable(() -> {
|
||||
this.bootstrap = new VotifierServerBootstrap(host, port, NuVotifier.this, disablev1);
|
||||
this.bootstrap.start(err -> {});
|
||||
}));
|
||||
getProxy().getScheduler().runAsync(this, initTask);
|
||||
try {
|
||||
@@ -251,22 +223,31 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
if ("none".equals(cacheMethod)) {
|
||||
getLogger().info("Vote cache none selected for caching: votes that cannot be immediately delivered will be lost.");
|
||||
} else if ("memory".equals(cacheMethod)) {
|
||||
voteCache = new MemoryVoteCache(ProxyServer.getInstance().getServers().size());
|
||||
voteCache = new MemoryVoteCache(
|
||||
this,
|
||||
fwdCfg.getInt("pluginMessaging.memory.cacheTime", -1));
|
||||
getLogger().info("Using in-memory cache for votes that are not able to be delivered.");
|
||||
} else if ("file".equals(cacheMethod)) {
|
||||
try {
|
||||
voteCache = new FileVoteCache(ProxyServer.getInstance().getServers().size(), this, new File(getDataFolder(),
|
||||
fwdCfg.getString("pluginMessaging.file.name")));
|
||||
voteCache = new FileVoteCache(
|
||||
this,
|
||||
new File(getDataFolder(), fwdCfg.getString("pluginMessaging.file.name")),
|
||||
fwdCfg.getInt("pluginMessaging.file.cacheTime", -1));
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE, "Unload to load file cache. Votes will be lost!", e);
|
||||
}
|
||||
}
|
||||
|
||||
int dumpRate = fwdCfg.getInt("pluginMessaging.dumpRate", 5);
|
||||
|
||||
ServerFilter filter = new ServerFilter(
|
||||
fwdCfg.getStringList("pluginMessaging.excludedServers"),
|
||||
fwdCfg.getBoolean("pluginMessaging.whitelist", false)
|
||||
);
|
||||
|
||||
if (!fwdCfg.getBoolean("pluginMessaging.onlySendToJoinedServer")) {
|
||||
|
||||
List<String> ignoredServers = fwdCfg.getStringList("pluginMessaging.excludedServers");
|
||||
|
||||
try {
|
||||
forwardingMethod = new PluginMessagingForwardingSource(channel, ignoredServers, this, voteCache);
|
||||
forwardingMethod = new PluginMessagingForwardingSource(channel, filter, this, voteCache, dumpRate);
|
||||
getLogger().info("Forwarding votes over PluginMessaging channel '" + channel + "' for vote forwarding!");
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
@@ -275,7 +256,8 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
try {
|
||||
String fallbackServer = fwdCfg.getString("pluginMessaging.joinedServerFallback", null);
|
||||
if (fallbackServer != null && fallbackServer.isEmpty()) fallbackServer = null;
|
||||
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, voteCache, fallbackServer);
|
||||
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, filter, voteCache, fallbackServer, dumpRate);
|
||||
getLogger().info("Forwarding votes over PluginMessaging channel '" + channel + "' for vote forwarding for online players!");
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
@@ -292,67 +274,114 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
getLogger().info("Address " + section.getString("address") + " couldn't be looked up. Ignoring!");
|
||||
continue;
|
||||
}
|
||||
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
|
||||
new InetSocketAddress(address, section.getShort("port")),
|
||||
KeyCreator.createKeyFrom(section.getString("token",section.getString("key"))));
|
||||
serverList.add(server);
|
||||
|
||||
Key token = null;
|
||||
try {
|
||||
token = KeyCreator.createKeyFrom(section.getString("token", section.getString("key")));
|
||||
} catch (IllegalArgumentException e) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"An exception occurred while attempting to add proxy target '" + s + "' - maybe your token is wrong? " +
|
||||
"Votes will not be forwarded to this server!", e);
|
||||
}
|
||||
|
||||
if (token != null) {
|
||||
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
|
||||
new InetSocketAddress(address, section.getInt("port")),
|
||||
token);
|
||||
serverList.add(server);
|
||||
}
|
||||
}
|
||||
|
||||
forwardingMethod = new ProxyForwardingVoteSource(this, serverGroup, serverList, null);
|
||||
forwardingMethod = bootstrap.createForwardingSource(serverList, null);
|
||||
getLogger().info("Forwarding votes from this NuVotifier instance to another NuVotifier server.");
|
||||
} else {
|
||||
getLogger().severe("No vote forwarding method '" + fwdMethod + "' known. Defaulting to noop implementation.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
public void onEnable() {
|
||||
scheduler = new BungeeScheduler(this);
|
||||
pluginLogger = new JavaUtilLogger(getLogger());
|
||||
|
||||
PluginManager pm = ProxyServer.getInstance().getPluginManager();
|
||||
pm.registerCommand(this, new NVReloadCmd(this));
|
||||
pm.registerCommand(this, new TestVoteCmd(this));
|
||||
pm.registerListener(this, new ReloadListener(this));
|
||||
|
||||
loadAndBind();
|
||||
}
|
||||
|
||||
private void halt() {
|
||||
// Shut down the network handlers.
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
forwardingMethod = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean reload() {
|
||||
try {
|
||||
halt();
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "On halt, an exception was thrown. This may be fine!", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
loadAndBind();
|
||||
getLogger().info("Reload was successful.");
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
halt();
|
||||
getLogger().log(Level.SEVERE, "On reload, there was a problem with the configuration. Votifier currently does nothing!", ex);
|
||||
} catch (Exception ex2) {
|
||||
getLogger().log(Level.SEVERE, "On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!", ex);
|
||||
getLogger().log(Level.SEVERE, "(halt exception)", ex2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
halt();
|
||||
getLogger().info("Votifier disabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion) 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 -> " + vote);
|
||||
getLogger().info("Got a protocol v1 vote record from " + remoteAddress + " -> " + vote);
|
||||
} else {
|
||||
getLogger().info("Got a protocol v2 vote record -> " + vote);
|
||||
getLogger().info("Got a protocol v2 vote record from " + remoteAddress + " -> " + vote);
|
||||
}
|
||||
}
|
||||
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getProxy().getPluginManager().callEvent(new VotifierEvent(vote));
|
||||
}
|
||||
});
|
||||
getProxy().getScheduler().runAsync(this, () -> getProxy().getPluginManager().callEvent(new VotifierEvent(vote)));
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
forwardingMethod.forward(vote);
|
||||
}
|
||||
});
|
||||
getProxy().getScheduler().runAsync(this, () -> forwardingMethod.forward(vote));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, Throwable throwable) {
|
||||
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
|
||||
if (debug) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
|
||||
if (alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + remoteAddress, throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + remoteAddress, throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + remoteAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,11 +396,29 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return getDescription().getVersion();
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return pluginLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BackendServer> getAllBackendServers() {
|
||||
return getProxy().getServers().values().stream()
|
||||
.map(BungeeBackendServer::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BackendServer> getServer(String name) {
|
||||
ServerInfo info = getProxy().getServerInfo(name);
|
||||
return Optional.ofNullable(info).map(BungeeBackendServer::new);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
/**
|
||||
* @author Joseph Hirschfeld
|
||||
* @date 12/31/2015
|
||||
*/
|
||||
public final class OnlineForwardPluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
|
||||
public OnlineForwardPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, ServerFilter serverFilter,
|
||||
VoteCache cache, String fallbackServer, int dumpRate) {
|
||||
super(channel, serverFilter, nuVotifier, cache, dumpRate);
|
||||
this.fallbackServer = fallbackServer;
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
private final String fallbackServer;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
ProxiedPlayer p = ProxyServer.getInstance().getPlayer(v.getUsername());
|
||||
if (p != null && p.getServer() != null &&
|
||||
serverFilter.isAllowed(p.getServer().getInfo().getName())) {
|
||||
if (forwardSpecific(new BungeeBackendServer(p.getServer().getInfo()), v)) {
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to server " + p.getServer().getInfo().getName());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ServerInfo serverInfo = ProxyServer.getInstance().getServers().get(fallbackServer);
|
||||
|
||||
// nowhere to fall back to, yet still not online. lets save this vote yet!
|
||||
if (serverInfo == null)
|
||||
attemptToAddToPlayerCache(v, v.getUsername());
|
||||
else if (!forwardSpecific(new BungeeBackendServer(serverInfo), v))
|
||||
attemptToAddToCache(v, fallbackServer);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getTag().equals(channel)) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
BackendServer server = new BungeeBackendServer(e.getServer().getInfo());
|
||||
handlePlayerSwitch(server, e.getPlayer().getName());
|
||||
onServerConnect(server);
|
||||
}
|
||||
}
|
||||
+9
-10
@@ -1,24 +1,23 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
|
||||
|
||||
public PluginMessagingForwardingSource(String channel, List<String> ignoredServers, NuVotifier nuVotifier, VoteCache cache) {
|
||||
super(channel, ignoredServers, nuVotifier, cache);
|
||||
public PluginMessagingForwardingSource(String channel, ServerFilter serverFilter, NuVotifier nuVotifier, VoteCache cache, int dumpRate) {
|
||||
super(channel, serverFilter, nuVotifier, cache, dumpRate);
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
protected PluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache) {
|
||||
super(channel, nuVotifier, voteCache);
|
||||
protected PluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache, int dumpRate) {
|
||||
super(channel, nuVotifier, voteCache, dumpRate);
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
@@ -29,6 +28,6 @@ public class PluginMessagingForwardingSource extends AbstractPluginMessagingForw
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
handleServerConnected(e);
|
||||
onServerConnect(new BungeeBackendServer(e.getServer().getInfo()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import net.md_5.bungee.api.event.ProxyReloadEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class ReloadListener implements Listener {
|
||||
private final NuVotifier nuVotifier;
|
||||
|
||||
public ReloadListener(NuVotifier nuVotifier) {
|
||||
this.nuVotifier = nuVotifier;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onProxyReload(ProxyReloadEvent event) {
|
||||
this.nuVotifier.reload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.vexsoftware.votifier.bungee.cmd;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class NVReloadCmd extends Command {
|
||||
|
||||
private final NuVotifier plugin;
|
||||
|
||||
private static final BaseComponent reloadingNuVotifier = new TextComponent("Reloading NuVotifier...");
|
||||
private static final BaseComponent nuvotifierHasBeenReloaded = new TextComponent("NuVotifier has been reloaded!");
|
||||
private static final BaseComponent problem = new TextComponent("Looks like there was a problem reloading NuVotifier, check the console!");
|
||||
private static final BaseComponent permission = new TextComponent("You do not have permission to do this!");
|
||||
|
||||
static {
|
||||
reloadingNuVotifier.setColor(ChatColor.GRAY);
|
||||
nuvotifierHasBeenReloaded.setColor(ChatColor.DARK_GREEN);
|
||||
problem.setColor(ChatColor.DARK_RED);
|
||||
permission.setColor(ChatColor.DARK_RED);
|
||||
}
|
||||
|
||||
public NVReloadCmd(NuVotifier plugin) {
|
||||
super("pnvreload", "nuvotifier.reload");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (sender.hasPermission("nuvotifier.reload")) {
|
||||
sender.sendMessage(reloadingNuVotifier);
|
||||
if (plugin.reload()) {
|
||||
sender.sendMessage(nuvotifierHasBeenReloaded);
|
||||
} else {
|
||||
sender.sendMessage(problem);
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage(permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.vexsoftware.votifier.bungee.cmd;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.ArgsToVote;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class TestVoteCmd extends Command {
|
||||
|
||||
private final NuVotifier plugin;
|
||||
|
||||
private static final BaseComponent permission = new TextComponent("You do not have permission to do this!");
|
||||
private static final BaseComponent usage = new TextComponent("Usage hint: /ptestvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]");
|
||||
|
||||
static {
|
||||
usage.setColor(ChatColor.GRAY);
|
||||
}
|
||||
|
||||
public TestVoteCmd(NuVotifier plugin) {
|
||||
super("ptestvote", "nuvotifier.testvote");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (sender.hasPermission("nuvotifier.testvote")) {
|
||||
Vote v;
|
||||
try {
|
||||
v = ArgsToVote.parse(args);
|
||||
} catch (IllegalArgumentException e) {
|
||||
TextComponent c = new TextComponent("Error while parsing arguments to create test vote: " + e.getMessage());
|
||||
c.setColor(ChatColor.DARK_RED);
|
||||
sender.sendMessage(c);
|
||||
sender.sendMessage(usage);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
|
||||
TextComponent c = new TextComponent("Test vote executed: " + v.toString());
|
||||
c.setColor(ChatColor.GREEN);
|
||||
sender.sendMessage(c);
|
||||
} else {
|
||||
sender.sendMessage(permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSource implements ForwardingVoteSource {
|
||||
|
||||
public AbstractPluginMessagingForwardingSource(String channel, List<String> ignoredServers, NuVotifier nuVotifier, VoteCache cache) {
|
||||
ProxyServer.getInstance().registerChannel(channel);
|
||||
this.channel = channel;
|
||||
this.nuVotifier = nuVotifier;
|
||||
this.cache = cache;
|
||||
this.ignoredServers = ignoredServers;
|
||||
}
|
||||
|
||||
protected AbstractPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache) {
|
||||
this(channel, null, nuVotifier, voteCache);
|
||||
}
|
||||
|
||||
protected final NuVotifier nuVotifier;
|
||||
protected final String channel;
|
||||
protected final VoteCache cache;
|
||||
protected final List<String> ignoredServers;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
for (ServerInfo s : ProxyServer.getInstance().getServers().values()) {
|
||||
if (ignoredServers != null && ignoredServers.contains(s.getName())) continue;
|
||||
if (!forwardSpecific(s, rawData)) attemptToAddToCache(v, s.getName());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean forwardSpecific(ServerInfo connection, Vote vote) {
|
||||
byte[] rawData = vote.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
return connection.sendData(channel, rawData, false);
|
||||
}
|
||||
|
||||
private boolean forwardSpecific(ServerInfo connection, byte[] data) {
|
||||
return connection.sendData(channel, data, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
ProxyServer.getInstance().unregisterChannel(channel);
|
||||
|
||||
if (cache != null && cache instanceof FileVoteCache) {
|
||||
try {
|
||||
((FileVoteCache) cache).halt();
|
||||
} catch (IOException e) {
|
||||
nuVotifier.getLogger().log(Level.SEVERE, "Unable to save cached votes, votes will be lost.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleServerConnected(final ServerConnectedEvent e) {
|
||||
if (cache == null) return;
|
||||
final String serverName = e.getServer().getInfo().getName();
|
||||
final Collection<Vote> cachedVotes = cache.evict(serverName);
|
||||
if (!cachedVotes.isEmpty()) {
|
||||
nuVotifier.getProxy().getScheduler().schedule(nuVotifier, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int evicted = 0;
|
||||
int unsuccessfulEvictions = 0;
|
||||
for (Vote v : cachedVotes) {
|
||||
if (forwardSpecific(e.getServer().getInfo(), v)) {
|
||||
evicted++;
|
||||
} else {
|
||||
// Re-add to cache to send later.
|
||||
cache.addToCache(v, serverName);
|
||||
unsuccessfulEvictions++;
|
||||
}
|
||||
}
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Successfully evicted " + evicted + " votes to server '" + serverName + "'.");
|
||||
if (unsuccessfulEvictions > 0) {
|
||||
nuVotifier.getLogger().info("Held " + unsuccessfulEvictions + " votes for server '" + serverName + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
protected void attemptToAddToCache(Vote v, String server) {
|
||||
if (cache != null) {
|
||||
cache.addToCache(v, server);
|
||||
if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().info("Added to forwarding cache: " + v + " -> " + server);
|
||||
} else if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().severe("Could not immediately send vote to backend, vote lost! " + v + " -> " + server);
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
/**
|
||||
* @author Joseph Hirschfeld
|
||||
* @date 12/31/2015
|
||||
*/
|
||||
public final class OnlineForwardPluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
|
||||
public OnlineForwardPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache cache, String fallbackServer) {
|
||||
super(channel, nuVotifier, cache);
|
||||
this.fallbackServer = fallbackServer;
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
private final String fallbackServer;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
ProxiedPlayer p = ProxyServer.getInstance().getPlayer(v.getUsername());
|
||||
if (p == null || !forwardSpecific(p.getServer().getInfo(), v)) {
|
||||
ServerInfo serverInfo = ProxyServer.getInstance().getServers().get(fallbackServer);
|
||||
if (serverInfo != null && !forwardSpecific(serverInfo, v)) {
|
||||
attemptToAddToCache(v, fallbackServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The below is to allow us to share code since the event handlers from the parent class don't get
|
||||
// picked up.
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getTag().equals(channel)) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
handleServerConnected(e);
|
||||
}
|
||||
}
|
||||
Vendored
-90
@@ -1,90 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class FileVoteCache extends MemoryVoteCache {
|
||||
|
||||
private final File cacheFile;
|
||||
private final ScheduledTask saveTask;
|
||||
|
||||
public FileVoteCache(int initialMemorySize, final Plugin plugin, File cacheFile) throws IOException {
|
||||
super(initialMemorySize);
|
||||
this.cacheFile = cacheFile;
|
||||
load();
|
||||
|
||||
saveTask = ProxyServer.getInstance().getScheduler().schedule(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
save();
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Unable to save cached votes, votes will be lost if you restart.", e);
|
||||
}
|
||||
}
|
||||
}, 3, 3, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void load() throws IOException {
|
||||
if (cacheFile.exists()) {
|
||||
// Load the cache from disk
|
||||
JSONObject object = new JSONObject(Files.toString(cacheFile, StandardCharsets.UTF_8));
|
||||
|
||||
// Deserialize all votes contained
|
||||
for (Object server : object.keySet()) {
|
||||
JSONArray voteArray = object.optJSONArray(((String) server));
|
||||
if (voteArray == null) continue;
|
||||
Set<Vote> votes = new LinkedHashSet<>(voteArray.length());
|
||||
for (int i = 0; i < voteArray.length(); i++) {
|
||||
votes.add(new Vote(voteArray.getJSONObject(i)));
|
||||
}
|
||||
voteCache.put(((String) server), votes);
|
||||
}
|
||||
} else {
|
||||
cacheFile.createNewFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
cacheLock.lock();
|
||||
JSONObject votesObject = new JSONObject();
|
||||
try {
|
||||
// Create a copy of the votes.
|
||||
for (Map.Entry<String, Collection<Vote>> entry : voteCache.entrySet()) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (Vote vote : entry.getValue()) {
|
||||
array.put(vote.serialize());
|
||||
}
|
||||
votesObject.put(entry.getKey(), array);
|
||||
}
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) {
|
||||
votesObject.write(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public void halt() throws IOException {
|
||||
saveTask.cancel();
|
||||
save();
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
-59
@@ -1,59 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class MemoryVoteCache implements VoteCache {
|
||||
|
||||
public MemoryVoteCache(int initialSize) {
|
||||
voteCache = new HashMap<>(initialSize);
|
||||
}
|
||||
|
||||
protected final Map<String, Collection<Vote>> voteCache;
|
||||
|
||||
protected final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
@Override
|
||||
public Collection<String> getCachedServers() {
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return ImmutableSet.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 LinkedHashSet<>();
|
||||
voteCache.put(server, voteCollection);
|
||||
}
|
||||
voteCollection.add(v);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evict(String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
Collection<Vote> fromCollection = voteCache.remove(server);
|
||||
return fromCollection != null ? ImmutableSet.copyOf(fromCollection) : ImmutableSet.<Vote>of();
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: NuVotifier
|
||||
main: com.vexsoftware.votifier.bungee.NuVotifier
|
||||
version: maven-version-number
|
||||
author: blakeman8192, Kramer, tuxed
|
||||
version: @app.version@
|
||||
author: Ichbinjoe, blakeman8192, Kramer, tuxed
|
||||
@@ -5,10 +5,6 @@ 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.
|
||||
@@ -26,21 +22,22 @@ forwarding:
|
||||
# - proxy - Proxies votes to other NuVotifier servers from this server.
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
channel: nuvotifier:votes
|
||||
|
||||
#Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
|
||||
#to them. If excludedServers is not present, votes will be sent to all servers.
|
||||
#If onlySendToJoinedServer is true, this option is ignored.
|
||||
|
||||
# If onlySendToJoinedServer is true and a player is on one of these servers, NuVotifier will process the vote as if the
|
||||
# player was not on the network at all.
|
||||
#excludedServers:
|
||||
#- server1
|
||||
#- server2
|
||||
|
||||
# If uncommented, turns excludedServers into an includedServers, where only servers on the list will be used.
|
||||
#whitelist: true
|
||||
|
||||
# Sets which cache to use if there are no players on the server that the vote is being sent to. Supported caches:
|
||||
# - none - Votes will simply be lost when no players are present on the server.
|
||||
# - memory - Votes will be cached in memory when no players are present. When Bungee is restarted, the votes will be lost.
|
||||
# This method is NOT recommended for production systems!
|
||||
# - file - Votes will be saved to a local file every 3 minutes and on shutdown and reloaded when Bungee is restarted. This is suitable for
|
||||
# most set ups.
|
||||
cache: file
|
||||
@@ -50,9 +47,17 @@ forwarding:
|
||||
#If you do not want the vote forwarded to a fallback, set this value to empty ('')
|
||||
#ONLY USED IF onlySendToJoinedServer is true!!
|
||||
joinedServerFallback: 'Hub'
|
||||
# Defines how quickly to dump votes over a player's connection when offloading a cache in votes per second
|
||||
dumpRate: 5
|
||||
# Options for file caching.
|
||||
file:
|
||||
name: cached-votes.json
|
||||
# days before a vote is considered 'dead' - removed from cache with a console warning
|
||||
cacheTime: 10
|
||||
# Options for memory caching.
|
||||
memory:
|
||||
# days before a vote is considered 'dead' and removed from memory. All votes are removed when the server restarts. -1 signifies no TTL
|
||||
cacheTime: -1
|
||||
# Specify servers to proxy votes for.
|
||||
proxy:
|
||||
Hub:
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
dependencies {
|
||||
implementation project(":nuvotifier-api")
|
||||
implementation group: "io.netty", name: "netty-handler", version: "4.1.49.Final"
|
||||
implementation group: "io.netty", name: "netty-transport-native-epoll", version: "4.1.49.Final", classifier: "linux-x86_64"
|
||||
implementation 'com.google.code.gson:gson:2.8.0'
|
||||
testImplementation group: "org.json", name: "json", version: "20180130" // retain this for testing reasons
|
||||
testImplementation 'com.google.guava:guava:28.1-jre'
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-common</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Netty Dependency -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-handler</artifactId>
|
||||
<version>4.0.31.Final</version>
|
||||
<type>jar</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- JSON Dependency -->
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20140107</version>
|
||||
<type>jar</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- JUnit, for tests -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -5,7 +5,11 @@ import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
public interface VoteHandler {
|
||||
void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception;
|
||||
|
||||
void onError(Channel channel, Throwable throwable);
|
||||
void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception;
|
||||
|
||||
default void onError(Throwable throwable, boolean voteAlreadyCompleted, String remoteAddress) {
|
||||
throw new RuntimeException("Unimplemented onError handler");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,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,125 @@
|
||||
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.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();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ public class VotifierSession {
|
||||
public static final AttributeKey<VotifierSession> KEY = AttributeKey.valueOf("votifier_session");
|
||||
private ProtocolVersion version = ProtocolVersion.UNKNOWN;
|
||||
private final String challenge;
|
||||
private boolean hasCompletedVote = false;
|
||||
|
||||
public VotifierSession() {
|
||||
challenge = TokenUtil.newToken();
|
||||
@@ -27,9 +28,26 @@ public class VotifierSession {
|
||||
return challenge;
|
||||
}
|
||||
|
||||
public void completeVote() {
|
||||
if (hasCompletedVote)
|
||||
throw new IllegalStateException("Protocol completed vote twice!");
|
||||
|
||||
hasCompletedVote = true;
|
||||
}
|
||||
|
||||
public boolean hasCompletedVote() {
|
||||
return hasCompletedVote;
|
||||
}
|
||||
|
||||
public enum ProtocolVersion {
|
||||
UNKNOWN,
|
||||
ONE,
|
||||
TWO
|
||||
UNKNOWN("unknown"),
|
||||
ONE("protocol v1"),
|
||||
TWO("protocol v2"),
|
||||
TEST("test");
|
||||
|
||||
public final String humanReadable;
|
||||
ProtocolVersion(String hr) {
|
||||
this.humanReadable = hr;
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
-12
@@ -1,49 +1,77 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.json.JSONObject;
|
||||
|
||||
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(vote, session.getVersion());
|
||||
handler.onVoteReceived(vote, session.getVersion(), ctx.channel().remoteAddress().toString());
|
||||
session.completeVote();
|
||||
|
||||
if (session.getVersion() == VotifierSession.ProtocolVersion.ONE) {
|
||||
ctx.close();
|
||||
} else {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("status", "ok");
|
||||
ctx.writeAndFlush(object.toString() + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("status", "ok");
|
||||
ctx.writeAndFlush(GsonInst.gson.toJson(object) + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
handler.onError(ctx.channel(), cause);
|
||||
String remoteAddr = ctx.channel().remoteAddress().toString();
|
||||
boolean hasCompletedVote = session.hasCompletedVote();
|
||||
|
||||
if (session.getVersion() == VotifierSession.ProtocolVersion.TWO) {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("status", "error");
|
||||
object.put("cause", cause.getClass().getSimpleName());
|
||||
object.put("error", cause.getMessage());
|
||||
ctx.writeAndFlush(object.toString() + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("status", "error");
|
||||
object.addProperty("cause", cause.getClass().getSimpleName());
|
||||
object.addProperty("error", cause.getMessage());
|
||||
ctx.writeAndFlush(GsonInst.gson.toJson(object) + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
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
-6
@@ -1,9 +1,9 @@
|
||||
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;
|
||||
|
||||
@@ -12,13 +12,16 @@ 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);
|
||||
}
|
||||
|
||||
+24
-9
@@ -1,9 +1,11 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
import 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;
|
||||
@@ -17,32 +19,45 @@ 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.
|
||||
throw new QuietException("Could not decrypt data from " + ctx.channel().remoteAddress() + " as it is too long. Attack?");
|
||||
}
|
||||
|
||||
byte[] block = ByteBufUtil.getBytes(buf);
|
||||
buf.skipBytes(buf.readableBytes());
|
||||
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
|
||||
try {
|
||||
block = RSA.decrypt(block, plugin.getProtocolV1Key().getPrivate());
|
||||
} catch (Exception e) {
|
||||
throw new CorruptedFrameException("Could not decrypt data (is your key correct?)", e);
|
||||
if (plugin.isDebug()) {
|
||||
throw new CorruptedFrameException("Could not decrypt data from " + ctx.channel().remoteAddress() + ". Make sure the public key on the list is correct.", e);
|
||||
} else {
|
||||
throw new QuietException("Could not decrypt data from " + ctx.channel().remoteAddress() + ". Make sure the public key on the list is correct.");
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the string we received.
|
||||
String all = new String(block, StandardCharsets.US_ASCII);
|
||||
String[] split = all.split("\n");
|
||||
if (split.length < 5) {
|
||||
throw new CorruptedFrameException("Not enough fields specified in vote.");
|
||||
throw new QuietException("Not enough fields specified in vote. This is not a NuVotifier issue. Got " + split.length + " fields, but needed 5.");
|
||||
}
|
||||
|
||||
if (!split[0].equals("VOTE")) {
|
||||
throw new CorruptedFrameException("VOTE opcode not found");
|
||||
throw new QuietException("The VOTE opcode was not present. This is not a NuVotifier issue, but a bug with the server list.");
|
||||
}
|
||||
|
||||
// Create the vote.
|
||||
|
||||
+15
-14
@@ -1,19 +1,19 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -25,42 +25,43 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, String s, List<Object> list) throws Exception {
|
||||
JSONObject voteMessage = new JSONObject(s);
|
||||
JsonObject voteMessage = GsonInst.gson.fromJson(s, JsonObject.class);
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
// Deserialize the payload.
|
||||
JSONObject votePayload = new JSONObject(voteMessage.getString("payload"));
|
||||
String payload = voteMessage.get("payload").getAsString();
|
||||
JsonObject votePayload = GsonInst.gson.fromJson(payload, JsonObject.class);
|
||||
|
||||
// Verify challenge.
|
||||
if (!votePayload.getString("challenge").equals(session.getChallenge())) {
|
||||
if (!votePayload.get("challenge").getAsString().equals(session.getChallenge())) {
|
||||
throw new CorruptedFrameException("Challenge is not valid");
|
||||
}
|
||||
|
||||
// Verify that we have keys available.
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
Key key = plugin.getTokens().get(votePayload.getString("serviceName"));
|
||||
Key key = plugin.getTokens().get(votePayload.get("serviceName").getAsString());
|
||||
|
||||
if (key == null) {
|
||||
key = plugin.getTokens().get("default");
|
||||
if (key == null) {
|
||||
throw new RuntimeException("Unknown service '" + votePayload.getString("serviceName") + "'");
|
||||
throw new RuntimeException("Unknown service '" + votePayload.get("serviceName").getAsString() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// Verify signature.
|
||||
String sigHash = voteMessage.getString("signature");
|
||||
byte[] sigBytes = DatatypeConverter.parseBase64Binary(sigHash);
|
||||
String sigHash = voteMessage.get("signature").getAsString();
|
||||
byte[] sigBytes = Base64.getDecoder().decode(sigHash);
|
||||
|
||||
if (!hmacEqual(sigBytes, voteMessage.getString("payload").getBytes(StandardCharsets.UTF_8), key)) {
|
||||
if (!hmacEqual(sigBytes, payload.getBytes(StandardCharsets.UTF_8), key)) {
|
||||
throw new CorruptedFrameException("Signature is not valid (invalid token?)");
|
||||
}
|
||||
|
||||
// Stopgap: verify the "uuid" field is valid, if provided.
|
||||
if (votePayload.has("uuid")) {
|
||||
UUID.fromString(votePayload.getString("uuid"));
|
||||
UUID.fromString(votePayload.get("uuid").getAsString());
|
||||
}
|
||||
|
||||
if (votePayload.getString("username").length() > 16) {
|
||||
if (votePayload.get("username").getAsString().length() > 16) {
|
||||
throw new CorruptedFrameException("Username too long");
|
||||
}
|
||||
|
||||
|
||||
+5
-7
@@ -1,10 +1,10 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.QuietException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.codec.string.StringEncoder;
|
||||
@@ -16,6 +16,8 @@ import java.util.List;
|
||||
* Attempts to determine if original protocol or protocol v2 is being used.
|
||||
*/
|
||||
public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
private static final QuietException V2_ONLY = new QuietException("This server only accepts well-formed Votifier v2 packets.");
|
||||
|
||||
private static final short PROTOCOL_2_MAGIC = 0x733A;
|
||||
private final boolean testMode;
|
||||
private final boolean allowv1;
|
||||
@@ -35,11 +37,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
return;
|
||||
}
|
||||
|
||||
short readMagic = buf.readShort();
|
||||
|
||||
// Reset reader index again
|
||||
buf.readerIndex(0);
|
||||
|
||||
short readMagic = buf.getShort(0);
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
if (readMagic == PROTOCOL_2_MAGIC) {
|
||||
@@ -55,7 +53,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
}
|
||||
} else {
|
||||
if (!allowv1) {
|
||||
throw new CorruptedFrameException("This server only accepts well-formed Votifier v2 packets.");
|
||||
throw V2_ONLY;
|
||||
}
|
||||
// Probably Protocol v1 Vote Message
|
||||
session.setVersion(VotifierSession.ProtocolVersion.ONE);
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
package com.vexsoftware.votifier.net.protocol.v1crypto;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -53,10 +54,22 @@ public class RSAIO {
|
||||
privateKey.getEncoded());
|
||||
try (FileOutputStream publicOut = new FileOutputStream(directory + "/public.key");
|
||||
FileOutputStream privateOut = new FileOutputStream(directory + "/private.key")) {
|
||||
publicOut.write(DatatypeConverter.printBase64Binary(publicSpec.getEncoded())
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
privateOut.write(DatatypeConverter.printBase64Binary(privateSpec.getEncoded())
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
publicOut.write(Base64.getEncoder().encode(publicSpec.getEncoded()));
|
||||
privateOut.write(Base64.getEncoder().encode(privateSpec.getEncoded()));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,16 +83,10 @@ 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 = DatatypeConverter.parseBase64Binary(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 = DatatypeConverter.parseBase64Binary(new String(
|
||||
encodedPrivateKey, StandardCharsets.UTF_8));
|
||||
byte[] encodedPrivateKey = readB64File(directory, "private.key");
|
||||
|
||||
// Instantiate and return the key pair.
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
public interface BackendServer {
|
||||
String getName();
|
||||
|
||||
boolean sendPluginMessage(String channel, byte[] data);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class JavaUtilLogger implements LoggingAdapter {
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
public JavaUtilLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s) {
|
||||
logger.severe(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s, Object... o) {
|
||||
logger.log(Level.SEVERE, s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s) {
|
||||
logger.warning(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s, Object... o) {
|
||||
logger.log(Level.WARNING, s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s) {
|
||||
logger.info(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s, Object... o) {
|
||||
logger.log(Level.INFO, s, o);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
public interface LoggingAdapter {
|
||||
void error(String s);
|
||||
void error(String s, Object... o);
|
||||
|
||||
void warn(String s);
|
||||
void warn(String s, Object... o);
|
||||
|
||||
void info(String s);
|
||||
void info(String s, Object... o);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ProxyVotifierPlugin extends VotifierPlugin {
|
||||
Collection<BackendServer> getAllBackendServers();
|
||||
|
||||
Optional<BackendServer> getServer(String name);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VotifierPlugin extends VoteHandler {
|
||||
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
|
||||
|
||||
Map<String, Key> getTokens();
|
||||
|
||||
KeyPair getProtocolV1Key();
|
||||
|
||||
LoggingAdapter getPluginLogger();
|
||||
|
||||
VotifierScheduler getScheduler();
|
||||
|
||||
default boolean isDebug() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
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 sync(Runnable runnable) {
|
||||
return new ScheduledVotifierTaskWrapper(service.submit(runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new ScheduledVotifierTaskWrapper(service.submit(runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.schedule(runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.schedule(runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.scheduleAtFixedRate(runnable, delay, repeat, unit));
|
||||
}
|
||||
|
||||
private static class ScheduledVotifierTaskWrapper implements ScheduledVotifierTask {
|
||||
private final Future<?> future;
|
||||
|
||||
private ScheduledVotifierTaskWrapper(Future<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
future.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
public interface ScheduledVotifierTask {
|
||||
void cancel();
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface VotifierScheduler {
|
||||
ScheduledVotifierTask sync(Runnable runnable);
|
||||
|
||||
ScheduledVotifierTask onPool(Runnable runnable);
|
||||
|
||||
ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit);
|
||||
|
||||
ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit);
|
||||
|
||||
ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.io.CharArrayReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSink implements ForwardingVoteSink {
|
||||
|
||||
public AbstractPluginMessagingForwardingSink(ForwardedVoteListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private final ForwardedVoteListener listener;
|
||||
|
||||
public void handlePluginMessage(byte[] message) {
|
||||
String strMessage = new String(message, StandardCharsets.UTF_8);
|
||||
try (CharArrayReader reader = new CharArrayReader(strMessage.toCharArray())) {
|
||||
JsonReader r = new JsonReader(reader);
|
||||
r.setLenient(true);
|
||||
while (r.peek() != JsonToken.END_DOCUMENT) {
|
||||
r.beginObject();
|
||||
JsonObject o = new JsonObject();
|
||||
|
||||
while (r.hasNext()) {
|
||||
String name = r.nextName();
|
||||
if (r.peek() == JsonToken.NUMBER)
|
||||
o.add(name, new JsonPrimitive(r.nextLong()));
|
||||
else
|
||||
o.add(name, new JsonPrimitive(r.nextString()));
|
||||
}
|
||||
r.endObject();
|
||||
|
||||
Vote v = new Vote(o);
|
||||
listener.onForward(v);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(); // Should never happen.
|
||||
}
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
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);
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
} else {
|
||||
cb.accept(cachedVotes);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handlePlayerSwitch(BackendServer server, String playerName) {
|
||||
if (cache == null) return;
|
||||
if (!serverFilter.isAllowed(server.getName())) return;
|
||||
|
||||
final Collection<Vote> cachedVotes = cache.evictPlayer(playerName);
|
||||
dumpVotesToServer(cachedVotes, server, "player '" + playerName + "'", failedVotes -> {
|
||||
for (Vote v : failedVotes)
|
||||
cache.addToCachePlayer(v, playerName);
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.sponge.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
/**
|
||||
* Represents a method at which to receive forwarded votes.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class ServerFilter {
|
||||
|
||||
public ServerFilter(Collection<String> names, boolean whitelist) {
|
||||
this.names = names;
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
public ServerFilter() {
|
||||
this(Collections.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
+175
@@ -0,0 +1,175 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-1
@@ -1,8 +1,9 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Represents a method of caching votes for forwarding to a server that it was not previously capable of sending.
|
||||
@@ -24,6 +25,16 @@ public interface VoteCache {
|
||||
*/
|
||||
void addToCache(Vote v, String server);
|
||||
|
||||
/**
|
||||
* Adds a vote to the vote cache for later sending, specifically keyed to a player. This is for the instance when the
|
||||
* player is rewarded, however is not online any servers able to recieve said vote.
|
||||
*
|
||||
* @param v Vote to add to the cache
|
||||
* @param player Server to add vote under
|
||||
*/
|
||||
default void addToCachePlayer(Vote v, String player) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all votes from the vote cache and returns a collection of vote objects
|
||||
*
|
||||
@@ -31,4 +42,15 @@ public interface VoteCache {
|
||||
* @return A collection of all votes which were previously in the cache under the server
|
||||
*/
|
||||
Collection<Vote> evict(String server);
|
||||
|
||||
/**
|
||||
* Evicts all votes from the vote cache for a specific player not assigned to a server and returns a collection of
|
||||
* vote objects
|
||||
*
|
||||
* @param player Player name of the votes to evict from the cache
|
||||
* @return A collection of all votes which were previously in the cache under the player
|
||||
*/
|
||||
default Collection<Vote> evictPlayer(String player) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
}
|
||||
+24
-36
@@ -1,18 +1,16 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierProtocol2Encoder;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierResponseHandler;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2Encoder;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierResponseHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.Delimiters;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
@@ -23,20 +21,20 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
private static final int MAX_RETRIES = 5;
|
||||
private final NuVotifier plugin;
|
||||
private final NioEventLoopGroup eventLoopGroup;
|
||||
private final VotifierPlugin plugin;
|
||||
private final Supplier<Bootstrap> nettyBootstrap;
|
||||
private final List<BackendServer> backendServers;
|
||||
private final VoteCache voteCache;
|
||||
|
||||
private static final StringDecoder STRING_DECODER = new StringDecoder(StandardCharsets.US_ASCII);
|
||||
|
||||
public ProxyForwardingVoteSource(NuVotifier plugin, NioEventLoopGroup eventLoopGroup, List<BackendServer> backendServers, VoteCache voteCache) {
|
||||
public ProxyForwardingVoteSource(VotifierPlugin plugin, Supplier<Bootstrap> nettyBootstrap, List<BackendServer> backendServers, VoteCache voteCache) {
|
||||
this.plugin = plugin;
|
||||
this.eventLoopGroup = eventLoopGroup;
|
||||
this.nettyBootstrap = nettyBootstrap;
|
||||
this.backendServers = backendServers;
|
||||
this.voteCache = voteCache;
|
||||
}
|
||||
@@ -54,21 +52,19 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
|
||||
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
|
||||
new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(eventLoopGroup)
|
||||
.handler(new ChannelInitializer<NioSocketChannel>() {
|
||||
nettyBootstrap.get()
|
||||
.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
protected void initChannel(SocketChannel channel) {
|
||||
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(8, TimeUnit.SECONDS));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
|
||||
channel.pipeline().addLast(STRING_DECODER);
|
||||
channel.pipeline().addLast(new VotifierProtocol2Encoder(server.key));
|
||||
channel.pipeline().addLast(new VotifierProtocol2HandshakeHandler(v, new VotifierResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
|
||||
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,12 +76,9 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
})
|
||||
.connect(server.address)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -106,18 +99,13 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getLogger().log(Level.SEVERE, msg, cause);
|
||||
plugin.getPluginLogger().error(msg, cause);
|
||||
} else {
|
||||
plugin.getLogger().log(Level.SEVERE, msg);
|
||||
plugin.getPluginLogger().error(msg);
|
||||
}
|
||||
|
||||
if (willRetry) {
|
||||
plugin.getProxy().getScheduler().schedule(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
forwardVote(server, v, tries + 1);
|
||||
}
|
||||
}, nextDelay, TimeUnit.SECONDS);
|
||||
plugin.getScheduler().delayedOnPool(() -> forwardVote(server, v, tries + 1), nextDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
+9
-9
@@ -1,15 +1,15 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
|
||||
public class VotifierProtocol2Encoder extends MessageToByteEncoder<VoteRequest> {
|
||||
private static final short MAGIC = 0x733A;
|
||||
@@ -22,18 +22,18 @@ public class VotifierProtocol2Encoder extends MessageToByteEncoder<VoteRequest>
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, VoteRequest req, ByteBuf buf) throws Exception {
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payloadObject = req.getVote().serialize();
|
||||
payloadObject.put("challenge", req.getChallenge());
|
||||
JsonObject object = new JsonObject();
|
||||
JsonObject payloadObject = req.getVote().serialize();
|
||||
payloadObject.addProperty("challenge", req.getChallenge());
|
||||
String payload = payloadObject.toString();
|
||||
object.put("payload", payload);
|
||||
object.addProperty("payload", payload);
|
||||
|
||||
// Generate the MAC
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
mac.update(payload.getBytes(StandardCharsets.UTF_8));
|
||||
String computed = DatatypeConverter.printBase64Binary(mac.doFinal());
|
||||
object.put("signature", computed);
|
||||
String computed = Base64.getEncoder().encodeToString(mac.doFinal());
|
||||
object.addProperty("signature", computed);
|
||||
|
||||
// JSON message is ready for encoding.
|
||||
String finalMessage = object.toString();
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
@@ -9,16 +9,16 @@ import io.netty.handler.codec.CorruptedFrameException;
|
||||
public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final Vote toSend;
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
private final NuVotifier nuVotifier;
|
||||
private final VotifierPlugin nuVotifier;
|
||||
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, NuVotifier nuVotifier) {
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, VotifierPlugin nuVotifier) {
|
||||
this.toSend = toSend;
|
||||
this.responseHandler = responseHandler;
|
||||
this.nuVotifier = nuVotifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String s) {
|
||||
String[] handshakeContents = s.split(" ");
|
||||
if (handshakeContents.length != 3) {
|
||||
throw new CorruptedFrameException("Handshake is not valid.");
|
||||
@@ -26,7 +26,7 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
|
||||
|
||||
VoteRequest request = new VoteRequest(handshakeContents[2], toSend);
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Sent request: " + request.toString());
|
||||
nuVotifier.getPluginLogger().info("Sent request: " + request.toString());
|
||||
}
|
||||
ctx.writeAndFlush(request);
|
||||
ctx.pipeline().addLast(new VotifierProtocol2ResponseHandler(responseHandler));
|
||||
@@ -35,7 +35,7 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
|
||||
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
+9
-7
@@ -1,8 +1,9 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
@@ -12,19 +13,20 @@ public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandle
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
JSONObject object = new JSONObject(msg);
|
||||
String status = object.getString("status");
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||
JsonObject object = GsonInst.gson.fromJson(msg, JsonObject.class);
|
||||
String status = object.get("status").getAsString();
|
||||
if (status.equals("ok")) {
|
||||
responseHandler.onSuccess();
|
||||
} else {
|
||||
responseHandler.onFailure(new Exception("Remote server error: " + object.getString("cause") + ": " + object.getString("error")));
|
||||
responseHandler.onFailure(new Exception("Remote server error: " + object.get("cause").getAsString() +
|
||||
": " + object.get("error").getAsString()));
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
public interface VotifierResponseHandler {
|
||||
void onSuccess();
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ArgsToVote {
|
||||
|
||||
private static final Pattern argumentPattern = Pattern.compile("([a-zA-Z]+)=(\\S*)");
|
||||
|
||||
public static Vote parse(String[] arguments) {
|
||||
return parse(arguments, null);
|
||||
}
|
||||
|
||||
public static Vote parse(String[] arguments, Map<String, String> additionalArgs) {
|
||||
String serviceName = "TestVote";
|
||||
String username = null;
|
||||
String address = "127.0.0.1";
|
||||
long localTimestamp = System.currentTimeMillis();
|
||||
String timestamp = Long.toString(localTimestamp, 10);
|
||||
|
||||
for (String s : arguments) {
|
||||
Matcher m = argumentPattern.matcher(s);
|
||||
if (m.matches()) {
|
||||
String key = m.group(1).toLowerCase();
|
||||
String v = m.group(2);
|
||||
switch (key) {
|
||||
case "servicename":
|
||||
serviceName = v;
|
||||
break;
|
||||
case "username":
|
||||
if (v.length() > 16)
|
||||
throw new IllegalArgumentException("Illegal username - must be less than 16 characters long.");
|
||||
username = v;
|
||||
break;
|
||||
case "address":
|
||||
address = v;
|
||||
break;
|
||||
case "timestamp":
|
||||
timestamp = v;
|
||||
break;
|
||||
default:
|
||||
if (additionalArgs != null)
|
||||
additionalArgs.put(key, v);
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (s.length() > 16)
|
||||
throw new IllegalArgumentException("Illegal username - must be less than 16 characters long.");
|
||||
username = s; // ezpz
|
||||
}
|
||||
}
|
||||
|
||||
if (username == null)
|
||||
throw new IllegalArgumentException("Username not specified!");
|
||||
|
||||
return new Vote(serviceName, username, address, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class GsonInst {
|
||||
|
||||
public static final Gson gson = new GsonBuilder().create();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class IOUtil {
|
||||
public static byte[] readAllBytes(InputStream from) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(8192);
|
||||
byte[] buf = new byte[8192];
|
||||
int read;
|
||||
while ((read = from.read(buf)) != -1) {
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
public class QuietException extends Exception {
|
||||
public QuietException() {
|
||||
}
|
||||
|
||||
public QuietException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+30
-40
@@ -1,19 +1,13 @@
|
||||
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;
|
||||
@@ -21,45 +15,36 @@ import java.security.KeyPair;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
|
||||
public class StandaloneVotifierPlugin implements VotifierPlugin {
|
||||
private final Map<String, Key> tokens;
|
||||
private final VoteReceiver receiver;
|
||||
private final KeyPair v1Key;
|
||||
private final InetSocketAddress bind;
|
||||
private NioEventLoopGroup group;
|
||||
private final VotifierScheduler scheduler;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
public StandaloneVotifierPlugin(Map<String, Key> tokens, VoteReceiver receiver, KeyPair v1Key, InetSocketAddress bind) {
|
||||
this.receiver = receiver;
|
||||
this.bind = bind;
|
||||
this.tokens = Collections.unmodifiableMap(new HashMap<>(tokens));
|
||||
this.v1Key = v1Key;
|
||||
this.scheduler = new ScheduledExecutorServiceVotifierScheduler(Executors.newScheduledThreadPool(1));
|
||||
}
|
||||
|
||||
public Throwable start() throws Throwable {
|
||||
group = new NioEventLoopGroup(2);
|
||||
public void start() {
|
||||
start(o -> {});
|
||||
}
|
||||
|
||||
ChannelFuture future = new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(group)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(StandaloneVotifierPlugin.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, v1Key != null));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(StandaloneVotifierPlugin.this));
|
||||
}
|
||||
})
|
||||
.bind(bind)
|
||||
.syncUninterruptibly();
|
||||
|
||||
if (!future.isSuccess()) {
|
||||
return future.cause();
|
||||
} else {
|
||||
return null;
|
||||
public void start(Consumer<Throwable> error) {
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
}
|
||||
this.bootstrap = new VotifierServerBootstrap(bind.getHostString(), bind.getPort(), this, v1Key == null);
|
||||
this.bootstrap.start(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,17 +58,22 @@ public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.3.3";
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return new JavaUtilLogger(Logger.getAnonymousLogger());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(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, Throwable throwable) {
|
||||
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
|
||||
receiver.onException(throwable);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
@@ -12,6 +16,7 @@ import java.nio.file.Paths;
|
||||
import java.security.*;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -23,7 +28,7 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
URL resourceUrl = TestVotifierPlugin.class.getResource(u);
|
||||
Path resourcePath = Paths.get(resourceUrl.toURI());
|
||||
|
||||
return DatatypeConverter.parseBase64Binary(new String(Files.readAllBytes(resourcePath), StandardCharsets.UTF_8));
|
||||
return Base64.getDecoder().decode(new String(Files.readAllBytes(resourcePath), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -71,8 +76,18 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.3";
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebug() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void specificKeysOnly() {
|
||||
@@ -84,4 +99,9 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
keyMap.clear();
|
||||
keyMap.put("default", KeyCreator.createKeyFrom("test"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,6 @@ public class VoteUtil {
|
||||
builder.append(s).append('\n'); // naive join needed!
|
||||
}
|
||||
|
||||
return RSA.encrypt(builder.toString().getBytes(StandardCharsets.UTF_8), key);
|
||||
return RSA.encrypt(builder.toString().getBytes(StandardCharsets.US_ASCII), key);
|
||||
}
|
||||
}
|
||||
+12
-16
@@ -1,21 +1,23 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
import com.vexsoftware.votifier.util.QuietException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import org.junit.Test;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class VotifierProtocol1DecoderTest {
|
||||
private static final VotifierSession SESSION = new VotifierSession();
|
||||
@@ -50,24 +52,21 @@ public class VotifierProtocol1DecoderTest {
|
||||
byte[] encrypted = RSA.encrypt(bad.getBytes(StandardCharsets.UTF_8), TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
try {
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(encryptedByteBuf));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
@Test
|
||||
public void testFailureDecodeMissingField() throws Exception {
|
||||
verifyFailure("VOTE\nTest\ntest\ntest"); // missing field
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
@Test
|
||||
public void testFailureDecodeBadOpcode() throws Exception {
|
||||
verifyFailure("TEST\nTest\ntest\ntest\ntest\n");
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
@Test
|
||||
public void testFailureDecodeBadRsa() throws Exception {
|
||||
// Decode our bad RSA key
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
@@ -80,10 +79,7 @@ public class VotifierProtocol1DecoderTest {
|
||||
byte[] encrypted = VoteUtil.encodePOJOv1(new Vote("Test", "test", "test", "test"), badPublicKey);
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
try {
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, ()->channel.writeInbound(encryptedByteBuf));
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
+39
-45
@@ -1,21 +1,23 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class VotifierProtocol2DecoderTest {
|
||||
private static final VotifierSession SESSION = new VotifierSession();
|
||||
@@ -32,13 +34,14 @@ public class VotifierProtocol2DecoderTest {
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
payload.put("challenge", SESSION.getChallenge());
|
||||
object.put("payload", payload.toString());
|
||||
JsonObject payload = vote.serialize();
|
||||
payload.addProperty("challenge", SESSION.getChallenge());
|
||||
String payloadEncoded = GsonInst.gson.toJson(payload);
|
||||
object.put("payload", payloadEncoded);
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
object.put("signature",
|
||||
DatatypeConverter.printBase64Binary(mac.doFinal(payload.toString().getBytes(StandardCharsets.UTF_8))));
|
||||
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
if (expectSuccess) {
|
||||
assertTrue(channel.writeInbound(object.toString()));
|
||||
@@ -58,86 +61,77 @@ public class VotifierProtocol2DecoderTest {
|
||||
sendVote(new Vote("Test", "test", "test", "0"), TestVotifierPlugin.getI().getTokens().get("default"), true);
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void testFailureDecodeBadPacket() throws Exception {
|
||||
@Test
|
||||
public void testFailureDecodeBadPacket() {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
payload.put("challenge", SESSION.getChallenge());
|
||||
object.put("payload", payload.toString());
|
||||
JsonObject payload = vote.serialize();
|
||||
payload.addProperty("challenge", SESSION.getChallenge());
|
||||
object.put("payload", GsonInst.gson.toJson(payload));
|
||||
// We "forget" the signature.
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void testFailureDecodeBadVoteField() throws Exception {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
JsonObject payload = vote.serialize();
|
||||
String payloadEncoded = GsonInst.gson.toJson(payload);
|
||||
// We "forget" the challenge.
|
||||
object.put("payload", payload.toString());
|
||||
object.put("payload", payloadEncoded);
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(TestVotifierPlugin.getI().getTokens().get("default"));
|
||||
object.put("signature",
|
||||
DatatypeConverter.printBase64Binary(mac.doFinal(payload.toString().getBytes(StandardCharsets.UTF_8))));
|
||||
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void testFailureDecodeBadChallenge() throws Exception {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
JsonObject payload = vote.serialize();
|
||||
// We provide the wrong challenge.
|
||||
payload.put("challenge", "not a challenge for me");
|
||||
payload.addProperty("challenge", "not a challenge for me");
|
||||
object.put("payload", payload.toString());
|
||||
String payloadEncoded = GsonInst.gson.toJson(payload);
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(TestVotifierPlugin.getI().getTokens().get("default"));
|
||||
object.put("signature",
|
||||
DatatypeConverter.printBase64Binary(mac.doFinal(payload.toString().getBytes(StandardCharsets.UTF_8))));
|
||||
Base64.getEncoder().encode(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(object.toString()));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void testFailureDecodeNonExistentKey() throws Exception {
|
||||
TestVotifierPlugin.getI().specificKeysOnly();
|
||||
|
||||
Vote vote = new Vote("Bad Service", "test", "test", "0");
|
||||
|
||||
try {
|
||||
sendVote(vote, TestVotifierPlugin.getI().getTokens().get("Test"), false);
|
||||
} finally {
|
||||
TestVotifierPlugin.getI().restoreDefault();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> sendVote(vote, TestVotifierPlugin.getI().getTokens().get("Test"), false));
|
||||
|
||||
TestVotifierPlugin.getI().restoreDefault();
|
||||
}
|
||||
|
||||
@Test(expected = CorruptedFrameException.class)
|
||||
public void testFailureDecodeBadSignature() throws Exception {
|
||||
@Test
|
||||
public void testFailureDecodeBadSignature() {
|
||||
Vote vote = new Vote("Bad Service", "test", "test", "0");
|
||||
sendVote(vote, KeyCreator.createKeyFrom("BadKey"), false);
|
||||
assertThrows(CorruptedFrameException.class, () -> sendVote(vote, KeyCreator.createKeyFrom("BadKey"), false));
|
||||
}
|
||||
}
|
||||
+10
-17
@@ -2,13 +2,16 @@ package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.QuietException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class VotifierProtocolDifferentiatorTest {
|
||||
@Test
|
||||
@@ -25,7 +28,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
test.release();
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@@ -41,11 +43,10 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.TWO, session.getVersion());
|
||||
test.release();
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void failIfv1NotSupported() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
|
||||
|
||||
@@ -56,14 +57,11 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
test.writeByte(0);
|
||||
}
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
test.release();
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(test));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
@Test
|
||||
public void failOnBadPacketTest() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
|
||||
|
||||
@@ -72,12 +70,8 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
buf.writeByte(0);
|
||||
}
|
||||
|
||||
try {
|
||||
channel.writeInbound(buf);
|
||||
} finally {
|
||||
buf.release();
|
||||
channel.close();
|
||||
}
|
||||
assertThrows(DecoderException.class, () -> channel.writeInbound(buf));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,7 +88,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
encryptedByteBuf.release();
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class PMForwardingSinkTest {
|
||||
|
||||
@Test
|
||||
public void testSuccessfulMultiDecode() throws Exception {
|
||||
List<Vote> receivedVotes = new ArrayList<>();
|
||||
ForwardedVoteListener vl = receivedVotes::add;
|
||||
|
||||
List<Vote> sentVotes = new ArrayList<>(Arrays.asList(
|
||||
new Vote("serviceA", "usernameA", "1.1.1.1", "1546300800"),
|
||||
new Vote("serviceB", "usernameBBBBBBB", "1.2.23.4", "1514764800")
|
||||
));
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
|
||||
for (Vote v : sentVotes) {
|
||||
message.append(v.serialize());
|
||||
}
|
||||
|
||||
byte[] messageBytes = message.toString().getBytes(StandardCharsets.UTF_8);
|
||||
System.out.println(message.toString());
|
||||
|
||||
AbstractPluginMessagingForwardingSink sink = new AbstractPluginMessagingForwardingSink(vl) {
|
||||
@Override
|
||||
public void halt() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
sink.handlePluginMessage(messageBytes);
|
||||
|
||||
assertEquals(sentVotes.size(), receivedVotes.size());
|
||||
|
||||
for (int i = 0; i < receivedVotes.size(); i++) {
|
||||
assertEquals(sentVotes.get(i), receivedVotes.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class ArgsToVoteTest {
|
||||
@Test
|
||||
void handlesInvalidRequest() {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
ArgsToVote.parse(new String[0]);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesSingleUsername() {
|
||||
Vote vote = ArgsToVote.parse(new String[] { "dummy_user" });
|
||||
assertEquals("dummy_user", vote.getUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesSingleUsernameTooLong() {
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
ArgsToVote.parse(new String[] { "AVeryVeryVeryLongUsername" });
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "servicename", "address", "timestamp" })
|
||||
void handlesAdditionalKeywordArguments(String key) throws Exception {
|
||||
Vote vote = ArgsToVote.parse(new String[] { "username=abc123", key + "=dummy" });
|
||||
|
||||
// Find the getter on the Vote object
|
||||
for (Method method : Vote.class.getDeclaredMethods()) {
|
||||
if (method.getName().toLowerCase(Locale.ENGLISH).equals("get" + key)) {
|
||||
assertEquals("dummy", method.invoke(vote));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fail("Unable to find appropriate getter - this is a bug");
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesUsernameKeywordArgument() {
|
||||
Vote vote = ArgsToVote.parse(new String[] { "username=dummy_user" });
|
||||
assertEquals("dummy_user", vote.getUsername());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "servicename", "address", "timestamp" })
|
||||
void handlesMixedUsernameAndKeywordArgument(String key) throws Exception {
|
||||
Vote vote = ArgsToVote.parse(new String[] { "abc123", key + "=dummy" });
|
||||
|
||||
assertEquals("abc123", vote.getUsername());
|
||||
|
||||
// Find the getter on the Vote object
|
||||
for (Method method : Vote.class.getDeclaredMethods()) {
|
||||
if (method.getName().toLowerCase(Locale.ENGLISH).equals("get" + key)) {
|
||||
assertEquals("dummy", method.invoke(vote));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fail("Unable to find appropriate getter - this is a bug");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("powerSetArgs")
|
||||
void handlesMixedKeywordArguments(Set<String> keys) throws Exception {
|
||||
String[] args = new String[keys.size() + 1];
|
||||
args[0] = "abc123";
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
args[i + 1] = ImmutableList.copyOf(keys).get(i) + "=dummy";
|
||||
}
|
||||
Vote vote = ArgsToVote.parse(args);
|
||||
|
||||
assertEquals("abc123", vote.getUsername());
|
||||
|
||||
// Find the getters on the Vote object
|
||||
outer: for (String key : keys) {
|
||||
for (Method method : Vote.class.getDeclaredMethods()) {
|
||||
if (method.getName().toLowerCase(Locale.ENGLISH).equals("get" + key)) {
|
||||
assertEquals("dummy", method.invoke(vote));
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
fail("Unable to find appropriate getter - this is a bug");
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Set<String>> powerSetArgs() {
|
||||
return Sets.powerSet(ImmutableSet.of("servicename", "address", "timestamp")).stream();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
resources/
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
.PHONY: clean all run
|
||||
|
||||
all : resources/paper.jar resources/waterfall.jar resources/velocity.jar resources/sponge.jar resources/viaversion.jar
|
||||
|
||||
resources :
|
||||
mkdir resources
|
||||
|
||||
resources/paper.jar : resources
|
||||
wget -O resources/paper.jar https://papermc.io/api/v1/paper/1.16.2/182/download
|
||||
|
||||
resources/waterfall.jar : resources
|
||||
wget -O resources/waterfall.jar https://papermc.io/ci/job/Waterfall/lastSuccessfulBuild/artifact/Waterfall-Proxy/bootstrap/target/Waterfall.jar
|
||||
|
||||
resources/velocity.jar : resources
|
||||
wget -O resources/velocity.jar https://ci.velocitypowered.com/job/velocity-1.1.0/lastSuccessfulBuild/artifact/proxy/build/libs/velocity-proxy-1.1.0-SNAPSHOT-all.jar
|
||||
|
||||
resources/sponge.jar : resources
|
||||
wget -O resources/sponge.jar https://repo.spongepowered.org/maven/org/spongepowered/spongevanilla/1.12.2-7.3.0/spongevanilla-1.12.2-7.3.0.jar
|
||||
|
||||
resources/viaversion.jar : resources
|
||||
wget -O resources/viaversion.jar https://ci.viaversion.com/job/ViaVersion/459/artifact/jar/target/ViaVersion-3.1.1-SNAPSHOT.jar
|
||||
|
||||
clean :
|
||||
rm -r resources/
|
||||
|
||||
run :
|
||||
tmux new-session './p1/run.sh' \; \
|
||||
split-window -h './p2/run.sh' \; \
|
||||
split-window -h './s1/run.sh' \; \
|
||||
split-window -h './s2/run.sh' \; \
|
||||
select-layout even-horizontal
|
||||
@@ -0,0 +1,29 @@
|
||||
## Using this testbed
|
||||
|
||||
This testbed runs two proxies, labeled p1 and p2, as well as two servers behind it,
|
||||
labeled s1 and s2. Ports for where everything is bound is listed below.
|
||||
|
||||
First, download required sources using the following command:
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
Then, each time you want to launch a session, run:
|
||||
|
||||
```
|
||||
make run
|
||||
```
|
||||
|
||||
FYI, this only works in Linux afaik because thats what I do my development on.
|
||||
|
||||
# Ports
|
||||
|
||||
| Server | Server Software | MC Port | Votifier Port |
|
||||
| ------ | ---------------------- | ------- | ------------- |
|
||||
| p1 | Waterfall/BungeeCord | 25577 | 8192 |
|
||||
| p2 | Velocity | 25578 | 8193 |
|
||||
| s1 | Paper (1.16.1) | 25565 | 8194 |
|
||||
| s2 | SpongeVanilla (1.12.2) | 25566 | 8195 |
|
||||
|
||||
For compatibility with 1.16.1, s2 includes ViaVersion.
|
||||
@@ -0,0 +1,4 @@
|
||||
logs/
|
||||
modules/
|
||||
locations.yml
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
server_connect_timeout: 5000
|
||||
listeners:
|
||||
- query_port: 25577
|
||||
motd: '&4its a bungee cord surbur'
|
||||
tab_list: GLOBAL_PING
|
||||
query_enabled: false
|
||||
proxy_protocol: false
|
||||
forced_hosts: {}
|
||||
ping_passthrough: false
|
||||
priorities:
|
||||
- s1
|
||||
bind_local_address: true
|
||||
host: 0.0.0.0:25577
|
||||
max_players: 1
|
||||
tab_size: 60
|
||||
force_default_server: false
|
||||
remote_ping_cache: -1
|
||||
network_compression_threshold: 256
|
||||
permissions:
|
||||
default:
|
||||
- bungeecord.command.server
|
||||
- bungeecord.command.list
|
||||
admin:
|
||||
- bungeecord.command.alert
|
||||
- bungeecord.command.end
|
||||
- bungeecord.command.ip
|
||||
- bungeecord.command.reload
|
||||
log_pings: true
|
||||
connection_throttle_limit: 3
|
||||
prevent_proxy_connections: true
|
||||
timeout: 30000
|
||||
player_limit: -1
|
||||
ip_forward: true
|
||||
groups:
|
||||
md_5:
|
||||
- admin
|
||||
remote_ping_timeout: 5000
|
||||
connection_throttle: 1
|
||||
log_commands: false
|
||||
stats: bf8a54ea-2917-4b9e-b463-2c05bd7f1ac6
|
||||
online_mode: true
|
||||
forge_support: true
|
||||
disabled_commands: []
|
||||
servers:
|
||||
s1:
|
||||
motd: SERVER 1
|
||||
address: localhost:25565
|
||||
restricted: false
|
||||
s2:
|
||||
motd: SERVER 2
|
||||
address: localhost:25566
|
||||
restricted: false
|
||||
@@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
modules:
|
||||
- jenkins://cmd_alert
|
||||
- jenkins://cmd_find
|
||||
- jenkins://cmd_list
|
||||
- jenkins://cmd_send
|
||||
- jenkins://cmd_server
|
||||
- jenkins://reconnect_yaml
|
||||
@@ -0,0 +1,2 @@
|
||||
rsa/
|
||||
cached-votes.json
|
||||
@@ -0,0 +1,74 @@
|
||||
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
|
||||
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
|
||||
host: 0.0.0.0
|
||||
|
||||
# Port to listen for new votes on
|
||||
port: 8192
|
||||
|
||||
# Whether or not to print debug messages. In a production system, this should be set to false.
|
||||
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
|
||||
debug: true
|
||||
|
||||
# All tokens, labeled by the serviceName of each server list.
|
||||
tokens:
|
||||
# Default token for all server lists, if another isn't supplied.
|
||||
default: proxy1token
|
||||
|
||||
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
|
||||
# option is currently not recommended as most voting sites only support the old protocol at present.
|
||||
disable-v1-protocol: false
|
||||
|
||||
# Configuration section for all vote forwarding to NuVotifier
|
||||
forwarding:
|
||||
# Sets whether to set up a remote method for fowarding. Supported methods:
|
||||
# - none - Does not set up a forwarding method.
|
||||
# - pluginMessaging - Sets up plugin messaging.
|
||||
# - proxy - Proxies votes to other NuVotifier servers from this server.
|
||||
method: pluginMessaging
|
||||
pluginMessaging:
|
||||
channel: nuvotifier:votes
|
||||
|
||||
#Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
|
||||
#to them. If excludedServers is not present, votes will be sent to all servers.
|
||||
# If onlySendToJoinedServer is true and a player is on one of these servers, NuVotifier will process the vote as if the
|
||||
# player was not on the network at all.
|
||||
#excludedServers:
|
||||
#- server1
|
||||
#- server2
|
||||
|
||||
# If uncommented, turns excludedServers into an includedServers, where only servers on the list will be used.
|
||||
#whitelist: true
|
||||
|
||||
# Sets which cache to use if there are no players on the server that the vote is being sent to. Supported caches:
|
||||
# - none - Votes will simply be lost when no players are present on the server.
|
||||
# - memory - Votes will be cached in memory when no players are present. When Bungee is restarted, the votes will be lost.
|
||||
# - file - Votes will be saved to a local file every 3 minutes and on shutdown and reloaded when Bungee is restarted. This is suitable for
|
||||
# most set ups.
|
||||
cache: file
|
||||
# Only sends the vote to the server in which the player is currently joined.
|
||||
onlySendToJoinedServer: true
|
||||
#Sets which server to send the vote in the event the player is not online when the vote is cast
|
||||
#If you do not want the vote forwarded to a fallback, set this value to empty ('')
|
||||
#ONLY USED IF onlySendToJoinedServer is true!!
|
||||
joinedServerFallback: 's1'
|
||||
# Defines how quickly to dump votes over a player's connection when offloading a cache in votes per second
|
||||
dumpRate: 5
|
||||
# Options for file caching.
|
||||
file:
|
||||
name: cached-votes.json
|
||||
# days before a vote is considered 'dead' - removed from cache with a console warning
|
||||
cacheTime: 10
|
||||
# Options for memory caching.
|
||||
memory:
|
||||
# days before a vote is considered 'dead' and removed from memory. All votes are removed when the server restarts. -1 signifies no TTL
|
||||
cacheTime: -1
|
||||
# Specify servers to proxy votes for.
|
||||
proxy:
|
||||
Hub:
|
||||
address: 127.0.0.1
|
||||
port: 8194
|
||||
token: server1token
|
||||
Hub2:
|
||||
address: 127.0.0.1
|
||||
port: 8195
|
||||
token: server2token
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../../universal/build/libs/nuvotifier.jar
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../resources/waterfall.jar
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
cd $DIR
|
||||
|
||||
java -jar proxy.jar
|
||||
@@ -0,0 +1,9 @@
|
||||
allow_empty_packets: false
|
||||
throttling:
|
||||
tab_complete: 1000
|
||||
game_version: ''
|
||||
disable_modern_tab_limiter: true
|
||||
use_netty_dns_resolver: true
|
||||
disable_entity_metadata_rewrite: false
|
||||
log_initial_handler_connections: true
|
||||
log_server_list_ping: false
|
||||
@@ -0,0 +1 @@
|
||||
logs/
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../../universal/build/libs/nuvotifier.jar
|
||||
@@ -0,0 +1,2 @@
|
||||
rsa/
|
||||
cached-votes.json
|
||||
Loaded 100 of 172 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user