Refactor some stuff around
There are a _lot_ of changes, including a ton which segment dependencies better. So thats cool
This commit is contained in:
1 parent
d0089c1c0c
commit
3a2cdfd7ea
104 files changed
+1001
-2088
No files matched your search
@@ -1,4 +0,0 @@
|
||||
# nuvotifier-api
|
||||
|
||||
Files in this directory have a strong API promise - they really shouldn't
|
||||
change over time. Feel free to depend against it!
|
||||
@@ -1,5 +0,0 @@
|
||||
dependencies {
|
||||
compile 'com.google.code.gson:gson:2.8.0'
|
||||
testCompile "junit:junit:4.11"
|
||||
testCompile "org.mockito:mockito-core:2.+"
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Vex Software LLC
|
||||
* This file is part of Votifier.
|
||||
*
|
||||
* Votifier is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Votifier is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.vexsoftware.votifier.model;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* A model for a vote.
|
||||
*
|
||||
* @author Blake Beaupain
|
||||
*/
|
||||
public class Vote {
|
||||
|
||||
/**
|
||||
* The name of the vote service.
|
||||
*/
|
||||
private String serviceName;
|
||||
|
||||
/**
|
||||
* The username of the voter.
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* The address of the voter.
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* The date and time of the vote.
|
||||
*/
|
||||
private String timeStamp;
|
||||
|
||||
private byte[] additionalData;
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp) {
|
||||
this.serviceName = serviceName;
|
||||
this.username = username;
|
||||
this.address = address;
|
||||
this.timeStamp = timeStamp;
|
||||
this.additionalData = null;
|
||||
}
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp, byte[] additionalData) {
|
||||
this.serviceName = serviceName;
|
||||
this.username = username;
|
||||
this.address = address;
|
||||
this.timeStamp = timeStamp;
|
||||
this.additionalData = additionalData;
|
||||
}
|
||||
|
||||
private static String getTimestamp(JsonElement object) {
|
||||
try {
|
||||
return Long.toString(object.getAsLong());
|
||||
} catch (Exception e) {
|
||||
return object.getAsString();
|
||||
}
|
||||
}
|
||||
|
||||
public Vote(JsonObject jsonObject) {
|
||||
this(jsonObject.get("serviceName").getAsString(),
|
||||
jsonObject.get("username").getAsString(),
|
||||
jsonObject.get("address").getAsString(),
|
||||
getTimestamp(jsonObject.get("timestamp")));
|
||||
if (jsonObject.has("additionalData"))
|
||||
additionalData = Base64.getDecoder().decode(jsonObject.get("additionalData").getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String data;
|
||||
if (additionalData == null)
|
||||
data = "null";
|
||||
else
|
||||
data = Base64.getEncoder().encodeToString(additionalData);
|
||||
|
||||
return "Vote (from:" + serviceName + " username:" + username
|
||||
+ " address:" + address + " timeStamp:" + timeStamp
|
||||
+ " additionalData:" + data + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the serviceName.
|
||||
*
|
||||
* @param serviceName The new serviceName
|
||||
*/
|
||||
@Deprecated
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the serviceName.
|
||||
*
|
||||
* @return The serviceName
|
||||
*/
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the username.
|
||||
*
|
||||
* @param username The new username
|
||||
*/
|
||||
@Deprecated
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the username.
|
||||
*
|
||||
* @return The username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the address.
|
||||
*
|
||||
* @param address The new address
|
||||
*/
|
||||
@Deprecated
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the address.
|
||||
*
|
||||
* @return The address
|
||||
*/
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time stamp.
|
||||
*
|
||||
* @param timeStamp The new time stamp
|
||||
*/
|
||||
@Deprecated
|
||||
public void setTimeStamp(String timeStamp) {
|
||||
this.timeStamp = timeStamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time stamp.
|
||||
*
|
||||
* @return The time stamp
|
||||
*/
|
||||
public String getTimeStamp() {
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional data sent with the vote, if it exists.
|
||||
*
|
||||
* @return Additional data sent with the vote
|
||||
*/
|
||||
public byte[] getAdditionalData() {
|
||||
return additionalData;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Vote vote = (Vote) o;
|
||||
|
||||
if (!serviceName.equals(vote.serviceName)) return false;
|
||||
if (!username.equals(vote.username)) return false;
|
||||
if (!address.equals(vote.address)) return false;
|
||||
if (!timeStamp.equals(vote.timeStamp)) return false;
|
||||
return Arrays.equals(additionalData, vote.additionalData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = serviceName.hashCode();
|
||||
result = 31 * result + username.hashCode();
|
||||
result = 31 * result + address.hashCode();
|
||||
result = 31 * result + timeStamp.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package com.vexsoftware.votifier.model;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import org.junit.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"));
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -2,12 +2,13 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
allprojects {
|
||||
group 'com.vexsoftware'
|
||||
group 'io.ibj.nuvotifier'
|
||||
version '3.0.0-SNAPSHOT'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'java-library'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
dependencies {
|
||||
compileOnly group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
compile project(":common")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.ibj.nuvotifier.platform.platform.scheduler.ScheduledVotifierTask;
|
||||
import io.ibj.nuvotifier.platform.platform.scheduler.VotifierScheduler;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -29,12 +29,12 @@ import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.model.VotifierEvent;
|
||||
import com.vexsoftware.votifier.net.server.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.server.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.server.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 io.ibj.nuvotifier.legacyserver.protocol.v1crypto.RSAIO;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.v1crypto.RSAKeygen;
|
||||
import io.ibj.nuvotifier.platform.platform.JavaUtilLogger;
|
||||
import io.ibj.nuvotifier.platform.platform.LoggingAdapter;
|
||||
import io.ibj.nuvotifier.platform.platform.VotifierPlugin;
|
||||
import io.ibj.nuvotifier.platform.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dependencies {
|
||||
compileOnly group: "net.md-5", name: "bungeecord-api", version: "1.8-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
compile project(":common")
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import io.ibj.nuvotifier.platform.platform.BackendServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
|
||||
public class BungeeBackendServer implements BackendServer {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.ibj.nuvotifier.platform.platform.scheduler.ScheduledVotifierTask;
|
||||
import io.ibj.nuvotifier.platform.platform.scheduler.VotifierScheduler;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -7,12 +7,12 @@ import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.bungee.cmd.NVReloadCmd;
|
||||
import com.vexsoftware.votifier.bungee.cmd.TestVoteCmd;
|
||||
import com.vexsoftware.votifier.net.server.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 io.ibj.nuvotifier.platform.platform.BackendServer;
|
||||
import io.ibj.nuvotifier.platform.platform.JavaUtilLogger;
|
||||
import io.ibj.nuvotifier.platform.platform.LoggingAdapter;
|
||||
import io.ibj.nuvotifier.platform.platform.ProxyVotifierPlugin;
|
||||
import com.vexsoftware.votifier.bungee.events.VotifierEvent;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.ibj.nuvotifier.platform.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;
|
||||
@@ -21,8 +21,8 @@ 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.server.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.server.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.server.protocol.v1crypto.RSAKeygen;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.v1crypto.RSAIO;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import io.ibj.nuvotifier.platform.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
dependencies {
|
||||
compile project(":nuvotifier-api")
|
||||
compile project(":model")
|
||||
compile group: "io.netty", name: "netty-handler", version: "4.1.32.Final"
|
||||
compile 'com.google.code.gson:gson:2.8.0'
|
||||
compile 'com.google.guava:guava:24.1-jre'
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
|
||||
public interface VoteHandler {
|
||||
|
||||
void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception;
|
||||
|
||||
default void onError(Throwable throwable, boolean voteAlreadyCompleted, String remoteAddress) {
|
||||
throw new RuntimeException("Unimplemented onError handler");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
|
||||
public interface Capability {
|
||||
void onAttach(CapabilityRegistrar.Context c);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
public class CapabilityAdapter extends ChannelInboundHandlerAdapter {
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
super.exceptionCaught(ctx, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public final class CapabilityRegistrar {
|
||||
public interface Context {
|
||||
interface MessageDecoder<T> {
|
||||
T decode(JsonObject o);
|
||||
}
|
||||
|
||||
interface OnMessage<T> {
|
||||
void onMessage(ChannelHandlerContext ctx, T message);
|
||||
}
|
||||
void onActivate(Callable<ChannelHandlerContext> onActivate);
|
||||
<T> void onMessage(String type, MessageDecoder<T> decoder, OnMessage<T> handler);
|
||||
void onClose(Callable<ChannelHandlerContext> onClose);
|
||||
}
|
||||
|
||||
private static final class CapabilityEntry {
|
||||
private static final class CapabilityMessageHandler<T> {
|
||||
final Context.MessageDecoder<T> decoder;
|
||||
final Context.OnMessage<T> onMessage;
|
||||
|
||||
private CapabilityMessageHandler(Context.MessageDecoder<T> decoder, Context.OnMessage<T> onMessage) {
|
||||
this.decoder = decoder;
|
||||
this.onMessage = onMessage;
|
||||
}
|
||||
|
||||
public void onMessage(ChannelHandlerContext ctx, JsonObject o) {
|
||||
T obj = decoder.decode(o);
|
||||
onMessage.onMessage(ctx, obj);
|
||||
}
|
||||
}
|
||||
private Callable<ChannelHandlerContext> onActivate;
|
||||
private Callable<ChannelHandlerContext> onClose;
|
||||
private final Map<String, CapabilityMessageHandler> inboundHandlers = new HashMap<>();
|
||||
}
|
||||
|
||||
private final Map<String, CapabilityEntry> capabilities = new HashMap<>();
|
||||
|
||||
void registerCapability(String capabilityName, Capability c) {
|
||||
assert !capabilities.containsKey(capabilityName);
|
||||
|
||||
CapabilityEntry e = new CapabilityEntry();
|
||||
|
||||
c.onAttach(new Context() {
|
||||
@Override
|
||||
public void onActivate(Callable<ChannelHandlerContext> onActivate) {
|
||||
assert onActivate != null;
|
||||
e.onActivate = onActivate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void onMessage(String type, MessageDecoder<T> decoder, OnMessage<T> handler) {
|
||||
assert type != null;
|
||||
assert decoder != null;
|
||||
assert handler != null;
|
||||
assert !e.inboundHandlers.containsKey(type);
|
||||
e.inboundHandlers.put(type, new CapabilityEntry.CapabilityMessageHandler<>(decoder, handler));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Callable<ChannelHandlerContext> onClose) {
|
||||
assert onClose != null;
|
||||
e.onClose = onClose;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageCodec;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class GsonCodec extends ByteToMessageCodec<JsonObject> {
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, JsonObject msg, ByteBuf out) {
|
||||
String s = GsonInst.gson.toJson(msg);
|
||||
out.writeBytes(s.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||
byte[] b = new byte[in.readableBytes()];
|
||||
in.readBytes(b);
|
||||
out.add(GsonInst.gson.fromJson(new String(b, StandardCharsets.UTF_8), JsonObject.class));
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.vexsoftware.votifier.net.client.protocol.V3Hello;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.json.JsonObjectDecoder;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
public class Votifier3ClientBootstrap {
|
||||
|
||||
private final SocketAddress socketAddress;
|
||||
|
||||
public Votifier3ClientBootstrap(SocketAddress address) {
|
||||
this.socketAddress = address;
|
||||
}
|
||||
|
||||
public ChannelFuture start() throws SSLException {
|
||||
final SslContext ctx = SslContextBuilder.forClient().build();
|
||||
|
||||
return new Bootstrap().channel(NioSocketChannel.class)
|
||||
.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ch.pipeline().addLast(ctx.newHandler(ch.alloc()));
|
||||
ch.pipeline().addLast(new JsonObjectDecoder());
|
||||
ch.pipeline().addLast(new GsonCodec());
|
||||
ch.pipeline().addLast("hello", new Votifier3Greeter(
|
||||
hook,
|
||||
new V3Hello(token, 1, Lists.newArrayList("nuvotifier.3")),
|
||||
key -> "nuvotifier.1".equals(key) ? null : null
|
||||
)
|
||||
);
|
||||
}
|
||||
}).connect(socketAddress);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.net.client.protocol.V3Hello;
|
||||
import com.vexsoftware.votifier.net.client.protocol.V3HelloBack;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Votifier3Greeter extends ChannelInboundHandlerAdapter {
|
||||
private final V3Hello hello;
|
||||
private final Function<String, ChannelInboundHandlerAdapter> capabilityFactory;
|
||||
|
||||
public Votifier3Greeter(V3Hello hello,
|
||||
Function<String, ChannelInboundHandlerAdapter> capabilityFactory) {
|
||||
this.hello = hello;
|
||||
this.capabilityFactory = capabilityFactory;
|
||||
}
|
||||
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
ctx.writeAndFlush(hello.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
// At this stage, we can only accept a 'helloback' response
|
||||
JsonObject o = ((JsonObject) msg);
|
||||
if (!o.has("type"))
|
||||
throw new IllegalStateException("Server responded with a message without type field");
|
||||
|
||||
String oType = o.get("type").getAsString();
|
||||
|
||||
if (!"helloback".equals(oType))
|
||||
throw new IllegalStateException("Server responded with type '" + oType + "' but helloback was expected");
|
||||
|
||||
V3HelloBack packet = V3HelloBack.decode(o);
|
||||
boolean hasCap = false;
|
||||
List<String> caps = packet.getAcceptedCapabilities();
|
||||
for (String s : hello.getCapabilities()) {
|
||||
if (caps.contains(s)) {
|
||||
ChannelInboundHandlerAdapter a = this.capabilityFactory.apply(s);
|
||||
ctx.pipeline().addLast(a);
|
||||
hasCap = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCap)
|
||||
ctx.pipeline().remove("hello");
|
||||
else
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client;
|
||||
|
||||
import com.vexsoftware.votifier.net.client.protocol.V3Error;
|
||||
import com.vexsoftware.votifier.net.client.protocol.V3Vote;
|
||||
|
||||
public interface Votifier3Hook {
|
||||
void onError(Throwable t);
|
||||
|
||||
void onHangup();
|
||||
|
||||
void onRemoteError(V3Error e);
|
||||
|
||||
void onVote(V3Vote v);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class V3Error extends V3Message {
|
||||
|
||||
private final String error;
|
||||
private final boolean goodbye;
|
||||
|
||||
public V3Error(String error, boolean goodbye) {
|
||||
super("error");
|
||||
this.error = error;
|
||||
this.goodbye = goodbye;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject serialize() {
|
||||
JsonObject o = super.serialize();
|
||||
o.addProperty("error", error);
|
||||
o.addProperty("goodbye", goodbye);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static V3Error decode(JsonObject o) {
|
||||
return new V3Error(o.get("error").getAsString(), o.get("goodbye").getAsBoolean());
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client.protocol;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class V3Hello extends V3Message {
|
||||
private final String token;
|
||||
private final int protocolVersion;
|
||||
private final List<String> capabilities;
|
||||
|
||||
public V3Hello(String token, int protocolVersion, List<String> capabilities) {
|
||||
super("hello");
|
||||
this.token = token;
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public List<String> getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject serialize() {
|
||||
JsonObject o = super.serialize();
|
||||
o.addProperty("token", token);
|
||||
JsonArray a = new JsonArray();
|
||||
for (String c : capabilities)
|
||||
a.add(c);
|
||||
o.add("capabilities", a);
|
||||
o.addProperty("protocolVersion", protocolVersion);
|
||||
return o;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client.protocol;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class V3HelloBack extends V3Message {
|
||||
|
||||
private final List<String> acceptedCapabilities;
|
||||
|
||||
public V3HelloBack(List<String> acceptedCapabilities) {
|
||||
super("helloback");
|
||||
this.acceptedCapabilities = acceptedCapabilities;
|
||||
}
|
||||
|
||||
public List<String> getAcceptedCapabilities() {
|
||||
return acceptedCapabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject serialize() {
|
||||
JsonObject o = super.serialize();
|
||||
JsonArray array = new JsonArray();
|
||||
for (String s : acceptedCapabilities) {
|
||||
array.add(s);
|
||||
}
|
||||
o.add("capabilities", array);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static V3HelloBack decode(JsonObject o) {
|
||||
JsonArray a = o.get("capabilities").getAsJsonArray();
|
||||
List<String> c = new ArrayList<>();
|
||||
for (int i = 0; i < a.size(); i++)
|
||||
c.add(a.get(i).getAsString());
|
||||
return new V3HelloBack(c);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class V3Message {
|
||||
|
||||
private final String type;
|
||||
|
||||
public V3Message(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public JsonObject serialize() {
|
||||
JsonObject o = new JsonObject();
|
||||
o.addProperty("type", type);
|
||||
return o;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client.protocol;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
public class V3Vote extends V3Message {
|
||||
|
||||
private final Vote v;
|
||||
private final JsonElement voteId;
|
||||
|
||||
public V3Vote(Vote v, JsonElement voteId) {
|
||||
super("vote");
|
||||
this.v = v;
|
||||
this.voteId = voteId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject serialize() {
|
||||
JsonObject o = super.serialize();
|
||||
o.add("vote", v.serialize());
|
||||
o.add("id", voteId);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static V3Vote decode(JsonObject o) {
|
||||
return new V3Vote(new Vote(o.get("vote").getAsJsonObject()), o.get("voteId"));
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.client.protocol;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class V3VoteAck extends V3Message {
|
||||
|
||||
private final JsonElement id;
|
||||
|
||||
public V3VoteAck(JsonElement id) {
|
||||
super("voteack");
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject serialize() {
|
||||
JsonObject o = super.serialize();
|
||||
o.add("id", id);
|
||||
return o;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package com.vexsoftware.votifier.net.server;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.vexsoftware.votifier.net.server.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.server.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.server.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class VotifierServerBootstrap {
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final NioEventLoopGroup bossLoopGroup;
|
||||
private final NioEventLoopGroup eventLoopGroup;
|
||||
private final VotifierPlugin plugin;
|
||||
private final boolean v1Disable;
|
||||
|
||||
private Channel serverChannel;
|
||||
|
||||
public VotifierServerBootstrap(String host, int port, VotifierPlugin plugin, boolean v1Disable) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.plugin = plugin;
|
||||
this.v1Disable = v1Disable;
|
||||
this.bossLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO boss").build());
|
||||
this.eventLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO worker").build());
|
||||
}
|
||||
|
||||
public void start(Consumer<Throwable> error) {
|
||||
Objects.requireNonNull(error, "error");
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(bossLoopGroup, eventLoopGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(plugin);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !v1Disable));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(plugin));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
plugin.getPluginLogger().info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
|
||||
error.accept(null);
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
plugin.getPluginLogger().error("Votifier was not able to bind to " + socketAddress.toString(), future.cause());
|
||||
error.accept(future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Bootstrap client() {
|
||||
return new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(eventLoopGroup);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (serverChannel != null) {
|
||||
try {
|
||||
serverChannel.close().syncUninterruptibly();
|
||||
} catch (Exception e) {
|
||||
plugin.getPluginLogger().error("Unable to shutdown server channel", e);
|
||||
}
|
||||
}
|
||||
eventLoopGroup.shutdownGracefully();
|
||||
bossLoopGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VotifierPlugin extends VoteHandler {
|
||||
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
|
||||
|
||||
Map<String, Key> getTokens();
|
||||
|
||||
KeyPair getProtocolV1Key();
|
||||
|
||||
LoggingAdapter getPluginLogger();
|
||||
|
||||
VotifierScheduler getScheduler();
|
||||
|
||||
default boolean isDebug() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.io.CharArrayReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSink implements ForwardingVoteSink {
|
||||
|
||||
public AbstractPluginMessagingForwardingSink(ForwardedVoteListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private final ForwardedVoteListener listener;
|
||||
|
||||
public void handlePluginMessage(byte[] message) {
|
||||
String strMessage = new String(message, StandardCharsets.UTF_8);
|
||||
try (CharArrayReader reader = new CharArrayReader(strMessage.toCharArray())) {
|
||||
JsonReader r = new JsonReader(reader);
|
||||
r.setLenient(true);
|
||||
while (r.peek() != JsonToken.END_DOCUMENT) {
|
||||
r.beginObject();
|
||||
JsonObject o = new JsonObject();
|
||||
|
||||
while (r.hasNext()) {
|
||||
String name = r.nextName();
|
||||
if (r.peek() == JsonToken.NUMBER)
|
||||
o.add(name, new JsonPrimitive(r.nextLong()));
|
||||
else
|
||||
o.add(name, new JsonPrimitive(r.nextString()));
|
||||
}
|
||||
r.endObject();
|
||||
|
||||
Vote v = new Vote(o);
|
||||
listener.onForward(v);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(); // Should never happen.
|
||||
}
|
||||
}
|
||||
}
|
||||
-156
@@ -1,156 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSource implements ForwardingVoteSource {
|
||||
|
||||
public AbstractPluginMessagingForwardingSource(String channel, ServerFilter serverFilter, ProxyVotifierPlugin plugin, VoteCache cache, int dumpRate) {
|
||||
this.channel = channel;
|
||||
this.plugin = plugin;
|
||||
this.cache = cache;
|
||||
this.serverFilter = serverFilter;
|
||||
this.dumpRate = dumpRate;
|
||||
}
|
||||
|
||||
protected AbstractPluginMessagingForwardingSource(String channel, ProxyVotifierPlugin plugin, VoteCache voteCache, int dumpRate) {
|
||||
this(channel, null, plugin, voteCache, dumpRate);
|
||||
}
|
||||
|
||||
protected final ProxyVotifierPlugin plugin;
|
||||
protected final String channel;
|
||||
protected final VoteCache cache;
|
||||
protected final ServerFilter serverFilter;
|
||||
private final int dumpRate;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
for (BackendServer server : plugin.getAllBackendServers()) {
|
||||
if (!serverFilter.isAllowed(server.getName())) continue;
|
||||
if (!forwardSpecific(server, rawData)) attemptToAddToCache(v, server.getName());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
/**
|
||||
* Represents a method to call when a vote is forwarded through the {@link ForwardingVoteSink}.
|
||||
*/
|
||||
public interface ForwardedVoteListener {
|
||||
|
||||
/**
|
||||
* Called whenever a vote is forwarded from a {@link ForwardingVoteSink}
|
||||
*
|
||||
* @param v Vote that was forwarded
|
||||
*/
|
||||
void onForward(Vote v);
|
||||
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
/**
|
||||
* Represents a method at which to receive forwarded votes.
|
||||
*/
|
||||
public interface ForwardingVoteSink {
|
||||
|
||||
/**
|
||||
* Stop or close any oustanding network interfaces. Occurs on the onDisable method of a plugin.
|
||||
*/
|
||||
void halt();
|
||||
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
/**
|
||||
* Represents a source for forwarding votes through to servers.
|
||||
*/
|
||||
public interface ForwardingVoteSource {
|
||||
|
||||
/**
|
||||
* Forwards a vote to all servers set up to receive votes.
|
||||
*
|
||||
* @param v Vote to forward to servers
|
||||
*/
|
||||
void forward(Vote v);
|
||||
|
||||
/**
|
||||
* Stop or close any oustanding network interfaces. Occurs on the onDisable method of a plugin.
|
||||
*/
|
||||
void halt();
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class ServerFilter {
|
||||
|
||||
public ServerFilter(Collection<String> names, boolean whitelist) {
|
||||
this.names = names;
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
public ServerFilter() {
|
||||
this(Collections.EMPTY_SET, true);
|
||||
}
|
||||
|
||||
private final Collection<String> names;
|
||||
private final boolean whitelist;
|
||||
|
||||
public boolean isAllowed(String name) {
|
||||
return names.contains(name) == whitelist;
|
||||
}
|
||||
}
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class FileVoteCache extends MemoryVoteCache {
|
||||
|
||||
private final LoggingAdapter l;
|
||||
private final File cacheFile;
|
||||
private final ScheduledVotifierTask saveTask;
|
||||
|
||||
public FileVoteCache(int initialMemorySize, final VotifierPlugin plugin, File cacheFile, long voteTTL) throws IOException {
|
||||
super(initialMemorySize, plugin, voteTTL);
|
||||
this.cacheFile = cacheFile;
|
||||
this.l = plugin.getPluginLogger();
|
||||
|
||||
load();
|
||||
|
||||
saveTask = plugin.getScheduler().repeatOnPool(() -> {
|
||||
try {
|
||||
save();
|
||||
} catch (IOException e) {
|
||||
l.error("Unable to save cached votes, votes will be lost if you restart.", e);
|
||||
}
|
||||
}, 3, 3, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private static Set<String> keySet(JsonObject object) {
|
||||
Set<String> set = new HashSet<>();
|
||||
for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
|
||||
set.add(entry.getKey());
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private void load() throws IOException {
|
||||
// Load the cache from disk
|
||||
JsonObject object;
|
||||
try (BufferedReader reader = Files.newReader(cacheFile, StandardCharsets.UTF_8)) {
|
||||
object = GsonInst.gson.fromJson(reader, JsonObject.class);
|
||||
if (object == null)
|
||||
// When the input is not malformed but instead empty, this returns null. Simply assume it is empty.
|
||||
object = new JsonObject();
|
||||
} catch (FileNotFoundException e) {
|
||||
object = new JsonObject();
|
||||
}
|
||||
|
||||
boolean resave = false;
|
||||
|
||||
// First, lets figure out if we are converting from a pre2.3.6 cache
|
||||
if (!object.has("players") || !object.has("servers") || !object.has("version") || object.size() != 3) {
|
||||
JsonObject oldObject = object;
|
||||
object = new JsonObject();
|
||||
object.add("servers", oldObject);
|
||||
object.add("players", new JsonObject());
|
||||
object.addProperty("version", 2);
|
||||
resave = true;
|
||||
}
|
||||
|
||||
if (object.get("version").getAsInt() != 2)
|
||||
throw new IllegalStateException("Could not read cache file! Unknown version '" + object.get("version").getAsInt() + "' read.");
|
||||
|
||||
JsonObject players = object.getAsJsonObject("players");
|
||||
JsonObject servers = object.getAsJsonObject("servers");
|
||||
|
||||
for (String player : keySet(players)) {
|
||||
playerVoteCache.putAll(player, readVotes(players.getAsJsonArray(player)));
|
||||
}
|
||||
|
||||
for (String server : keySet(servers)) {
|
||||
voteCache.putAll(server, readVotes(servers.getAsJsonArray(server)));
|
||||
}
|
||||
|
||||
if (resave) {
|
||||
File replacementFile;
|
||||
for (int i = 0; ; i++) {
|
||||
replacementFile = new File(cacheFile.getParentFile(), cacheFile.getName() + ".bak." + i);
|
||||
if (!replacementFile.exists())
|
||||
break;
|
||||
}
|
||||
|
||||
if (!cacheFile.renameTo(replacementFile)) {
|
||||
l.error("Backup movement failed! Will not save.");
|
||||
return;
|
||||
}
|
||||
|
||||
l.warn("Saving new vote cache format to file - backup moved to " + replacementFile.getAbsolutePath());
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Vote> readVotes(JsonArray voteArray) {
|
||||
List<Vote> votes = new ArrayList<>(voteArray.size());
|
||||
for (int i = 0; i < voteArray.size(); i++) {
|
||||
JsonObject voteObject = voteArray.get(i).getAsJsonObject();
|
||||
Vote v = new Vote(voteObject);
|
||||
if (hasTimedOut(v))
|
||||
l.warn("Purging out of date vote.", v);
|
||||
else
|
||||
votes.add(v);
|
||||
}
|
||||
return votes;
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
cacheLock.lock();
|
||||
JsonObject votesObject = new JsonObject();
|
||||
votesObject.addProperty("version", 2);
|
||||
try {
|
||||
votesObject.add("players", serializeMap(playerVoteCache.asMap()));
|
||||
votesObject.add("servers", serializeMap(voteCache.asMap()));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) {
|
||||
GsonInst.gson.toJson(votesObject, writer);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonObject serializeMap(Map<String, Collection<Vote>> map) {
|
||||
JsonObject o = new JsonObject();
|
||||
|
||||
Iterator<Map.Entry<String, Collection<Vote>>> entryItr = map.entrySet().iterator();
|
||||
while (entryItr.hasNext()) {
|
||||
Map.Entry<String, Collection<Vote>> entry = entryItr.next();
|
||||
JsonArray array = new JsonArray();
|
||||
Iterator<Vote> voteItr = entry.getValue().iterator();
|
||||
while (voteItr.hasNext()) {
|
||||
Vote vote = voteItr.next();
|
||||
|
||||
// if the vote is no longer valid, notify and remove
|
||||
if (hasTimedOut(vote)) {
|
||||
l.warn("Purging out of date vote.", vote);
|
||||
voteItr.remove();
|
||||
} else {
|
||||
array.add(vote.serialize());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if, during our iteration, we TTL invalidated all of the votes
|
||||
if (entry.getValue().isEmpty())
|
||||
entryItr.remove();
|
||||
o.add(entry.getKey(), array);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
public void halt() throws IOException {
|
||||
saveTask.cancel();
|
||||
save();
|
||||
}
|
||||
}
|
||||
Vendored
-122
@@ -1,122 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class MemoryVoteCache implements VoteCache {
|
||||
|
||||
private final LoggingAdapter l;
|
||||
private final long voteTTL;
|
||||
|
||||
protected final Multimap<String, Vote> voteCache;
|
||||
protected final Multimap<String, Vote> playerVoteCache;
|
||||
|
||||
protected final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
private final ScheduledVotifierTask sweepTask;
|
||||
|
||||
public MemoryVoteCache(int initialSize, VotifierPlugin p, long voteTTL) {
|
||||
voteCache = HashMultimap.create(initialSize, 10); // 10 is a complete guess. I have no idea .-.
|
||||
playerVoteCache = HashMultimap.create();
|
||||
|
||||
this.voteTTL = voteTTL;
|
||||
|
||||
this.l = p.getPluginLogger();
|
||||
this.sweepTask = p.getScheduler().repeatOnPool(this::sweep, 12, 12, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getCachedServers() {
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return ImmutableList.copyOf(voteCache.keySet());
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCache(Vote v, String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
voteCache.put(server, v);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCachePlayer(Vote v, String player) {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
playerVoteCache.put(player, v);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evictPlayer(String player) {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return new HashSet<>(playerVoteCache.removeAll(player));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evict(String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return new HashSet<>(voteCache.removeAll(server));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void sweep() {
|
||||
cacheLock.lock();
|
||||
try {
|
||||
sweep(voteCache);
|
||||
sweep(playerVoteCache);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void sweep(Multimap<?, Vote> m) {
|
||||
Iterator<Vote> vi = m.values().iterator();
|
||||
while (vi.hasNext()) {
|
||||
Vote v = vi.next();
|
||||
if (hasTimedOut(v)) {
|
||||
l.warn("Purging out of date vote.", v);
|
||||
vi.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasTimedOut(Vote v) {
|
||||
if (voteTTL == -1) return false;
|
||||
// TODO: FIX FOR 3.0 RELEASE
|
||||
// scale voteTTL to milliseconds
|
||||
return false;
|
||||
//return v.getLocalTimestamp() + voteTTL * 24 * 60 * 60 * 1000 < System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Represents a method of caching votes for forwarding to a server that it was not previously capable of sending.
|
||||
*/
|
||||
public interface VoteCache {
|
||||
|
||||
/**
|
||||
* Returns the names of all server in which the cache is holding votes for
|
||||
*
|
||||
* @return Collection of all cached servers
|
||||
*/
|
||||
Collection<String> getCachedServers();
|
||||
|
||||
/**
|
||||
* Adds a vote to the vote cache for later sending
|
||||
*
|
||||
* @param v Vote to add to cache
|
||||
* @param server Server to add vote under
|
||||
*/
|
||||
void addToCache(Vote v, String server);
|
||||
|
||||
/**
|
||||
* Adds a vote to the vote cache for later sending, specifically keyed to a player. This is for the instance when the
|
||||
* player is rewarded, however is not online any servers able to recieve said vote.
|
||||
*
|
||||
* @param v Vote to add to the cache
|
||||
* @param player Server to add vote under
|
||||
*/
|
||||
default void addToCachePlayer(Vote v, String player) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all votes from the vote cache and returns a collection of vote objects
|
||||
*
|
||||
* @param server Server name of which to evict the votes from the cache
|
||||
* @return A collection of all votes which were previously in the cache under the server
|
||||
*/
|
||||
Collection<Vote> evict(String server);
|
||||
|
||||
/**
|
||||
* Evicts all votes from the vote cache for a specific player not assigned to a server and returns a collection of
|
||||
* vote objects
|
||||
*
|
||||
* @param player Player name of the votes to evict from the cache
|
||||
* @return A collection of all votes which were previously in the cache under the player
|
||||
*/
|
||||
default Collection<Vote> evictPlayer(String player) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
}
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy;
|
||||
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2Encoder;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierResponseHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.Delimiters;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
private static final int MAX_RETRIES = 5;
|
||||
private final VotifierPlugin plugin;
|
||||
private final Supplier<Bootstrap> nettyBootstrap;
|
||||
private final List<BackendServer> backendServers;
|
||||
private final VoteCache voteCache;
|
||||
|
||||
private static final StringDecoder STRING_DECODER = new StringDecoder(StandardCharsets.US_ASCII);
|
||||
|
||||
public ProxyForwardingVoteSource(VotifierPlugin plugin, Supplier<Bootstrap> nettyBootstrap, List<BackendServer> backendServers, VoteCache voteCache) {
|
||||
this.plugin = plugin;
|
||||
this.nettyBootstrap = nettyBootstrap;
|
||||
this.backendServers = backendServers;
|
||||
this.voteCache = voteCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
for (final BackendServer server : backendServers) {
|
||||
forwardVote(server, v, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private int fib(int t) {
|
||||
if (t <= 1) return 1;
|
||||
return fib(t - 2) + fib(t - 1);
|
||||
}
|
||||
|
||||
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
|
||||
nettyBootstrap.get()
|
||||
.handler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
|
||||
channel.pipeline().addLast(STRING_DECODER);
|
||||
channel.pipeline().addLast(new VotifierProtocol2Encoder(server.key));
|
||||
channel.pipeline().addLast(new VotifierProtocol2HandshakeHandler(v, new VotifierResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable error) {
|
||||
handleFailure(server, v, error, tries);
|
||||
}
|
||||
}, plugin));
|
||||
}
|
||||
})
|
||||
.connect(server.address)
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleFailure(final BackendServer server, final Vote v, Throwable cause, final int tries) {
|
||||
int nextDelay = fib(tries + 1);
|
||||
boolean willRetry = tries < MAX_RETRIES;
|
||||
|
||||
String msg = "Unable to send vote to " + server.address + ".";
|
||||
if (willRetry) {
|
||||
msg += " Will retry sending in " + nextDelay + " second(s).";
|
||||
} else {
|
||||
if (voteCache == null) {
|
||||
msg += " This vote will be lost!";
|
||||
} else {
|
||||
voteCache.addToCache(v, server.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger().error(msg, cause);
|
||||
} else {
|
||||
plugin.getPluginLogger().error(msg);
|
||||
}
|
||||
|
||||
if (willRetry) {
|
||||
plugin.getScheduler().delayedOnPool(() -> forwardVote(server, v, tries + 1), nextDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
|
||||
}
|
||||
|
||||
public static class BackendServer {
|
||||
private final String name;
|
||||
private final InetSocketAddress address;
|
||||
private final Key key;
|
||||
|
||||
public BackendServer(String name, InetSocketAddress address, Key key) {
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
public class VoteRequest {
|
||||
private final String challenge;
|
||||
private final Vote vote;
|
||||
|
||||
public VoteRequest(String challenge, Vote vote) {
|
||||
this.challenge = challenge;
|
||||
this.vote = vote;
|
||||
}
|
||||
|
||||
public String getChallenge() {
|
||||
return challenge;
|
||||
}
|
||||
|
||||
public Vote getVote() {
|
||||
return vote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VoteRequest{" +
|
||||
"challenge='" + challenge + '\'' +
|
||||
", vote=" + vote +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
|
||||
public class VotifierProtocol2Encoder extends MessageToByteEncoder<VoteRequest> {
|
||||
private static final short MAGIC = 0x733A;
|
||||
|
||||
private final Key key;
|
||||
|
||||
public VotifierProtocol2Encoder(Key key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, VoteRequest req, ByteBuf buf) throws Exception {
|
||||
JsonObject object = new JsonObject();
|
||||
JsonObject payloadObject = req.getVote().serialize();
|
||||
payloadObject.addProperty("challenge", req.getChallenge());
|
||||
String payload = payloadObject.toString();
|
||||
object.addProperty("payload", payload);
|
||||
|
||||
// Generate the MAC
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
mac.update(payload.getBytes(StandardCharsets.UTF_8));
|
||||
String computed = Base64.getEncoder().encodeToString(mac.doFinal());
|
||||
object.addProperty("signature", computed);
|
||||
|
||||
// JSON message is ready for encoding.
|
||||
String finalMessage = object.toString();
|
||||
buf.writeShort(MAGIC);
|
||||
buf.writeShort(finalMessage.length());
|
||||
ByteBuf messageBytes = Unpooled.copiedBuffer(finalMessage, StandardCharsets.UTF_8);
|
||||
buf.writeBytes(messageBytes);
|
||||
messageBytes.release();
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
|
||||
public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final Vote toSend;
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
private final VotifierPlugin nuVotifier;
|
||||
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, VotifierPlugin nuVotifier) {
|
||||
this.toSend = toSend;
|
||||
this.responseHandler = responseHandler;
|
||||
this.nuVotifier = nuVotifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String s) {
|
||||
String[] handshakeContents = s.split(" ");
|
||||
if (handshakeContents.length != 3) {
|
||||
throw new CorruptedFrameException("Handshake is not valid.");
|
||||
}
|
||||
|
||||
VoteRequest request = new VoteRequest(handshakeContents[2], toSend);
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getPluginLogger().info("Sent request: " + request.toString());
|
||||
}
|
||||
ctx.writeAndFlush(request);
|
||||
ctx.pipeline().addLast(new VotifierProtocol2ResponseHandler(responseHandler));
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
|
||||
public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
|
||||
public VotifierProtocol2ResponseHandler(VotifierResponseHandler responseHandler) {
|
||||
this.responseHandler = responseHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||
JsonObject object = GsonInst.gson.fromJson(msg, JsonObject.class);
|
||||
String status = object.get("status").getAsString();
|
||||
if (status.equals("ok")) {
|
||||
responseHandler.onSuccess();
|
||||
} else {
|
||||
responseHandler.onFailure(new Exception("Remote server error: " + object.get("cause").getAsString() +
|
||||
": " + object.get("error").getAsString()));
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
public interface VotifierResponseHandler {
|
||||
void onSuccess();
|
||||
|
||||
void onFailure(Throwable error);
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
package com.vexsoftware.votifier.util.standalone;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.server.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledExecutorServiceVotifierScheduler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class StandaloneVotifierPlugin implements VotifierPlugin {
|
||||
private final Map<String, Key> tokens;
|
||||
private final VoteReceiver receiver;
|
||||
private final KeyPair v1Key;
|
||||
private final InetSocketAddress bind;
|
||||
private final VotifierScheduler scheduler;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
public StandaloneVotifierPlugin(Map<String, Key> tokens, VoteReceiver receiver, KeyPair v1Key, InetSocketAddress bind) {
|
||||
this.receiver = receiver;
|
||||
this.bind = bind;
|
||||
this.tokens = Collections.unmodifiableMap(new HashMap<>(tokens));
|
||||
this.v1Key = v1Key;
|
||||
this.scheduler = new ScheduledExecutorServiceVotifierScheduler(Executors.newScheduledThreadPool(1));
|
||||
}
|
||||
|
||||
public void start() {
|
||||
start(o -> {});
|
||||
}
|
||||
|
||||
public void start(Consumer<Throwable> error) {
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
}
|
||||
this.bootstrap = new VotifierServerBootstrap(bind.getHostString(), bind.getPort(), this, v1Key == null);
|
||||
this.bootstrap.start(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Key> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair getProtocolV1Key() {
|
||||
return v1Key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return new JavaUtilLogger(Logger.getAnonymousLogger());
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) throws Exception {
|
||||
receiver.onVote(vote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
|
||||
receiver.onException(throwable);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.vexsoftware.votifier.util.standalone;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
public interface VoteReceiver {
|
||||
void onVote(Vote vote) throws Exception;
|
||||
void onException(Throwable throwable);
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
package com.vexsoftware.votifier.util.standalone;
|
||||
|
||||
import com.vexsoftware.votifier.net.server.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.interfaces.RSAKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class VotifierServerBuilder {
|
||||
private final Map<String, Key> keyMap = new HashMap<>();
|
||||
private KeyPair v1Key;
|
||||
private InetSocketAddress bind;
|
||||
private VoteReceiver receiver;
|
||||
|
||||
public VotifierServerBuilder addToken(String service, String token) {
|
||||
Objects.requireNonNull(service, "service");
|
||||
Objects.requireNonNull(token, "key");
|
||||
|
||||
keyMap.put(service, KeyCreator.createKeyFrom(token));
|
||||
return this;
|
||||
}
|
||||
|
||||
public VotifierServerBuilder v1Key(KeyPair v1Key) {
|
||||
this.v1Key = Objects.requireNonNull(v1Key, "v1Key");
|
||||
if (!(v1Key.getPrivate() instanceof RSAKey)) {
|
||||
throw new IllegalArgumentException("Provided key is not an RSA key.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public VotifierServerBuilder v1KeyFolder(File file) throws Exception {
|
||||
this.v1Key = RSAIO.load(Objects.requireNonNull(file, "file"));
|
||||
return this;
|
||||
}
|
||||
|
||||
public VotifierServerBuilder bind(InetSocketAddress bind) {
|
||||
this.bind = Objects.requireNonNull(bind, "bind");
|
||||
return this;
|
||||
}
|
||||
|
||||
public VotifierServerBuilder receiver(VoteReceiver receiver) {
|
||||
this.receiver = Objects.requireNonNull(receiver, "receiver");
|
||||
return this;
|
||||
}
|
||||
|
||||
public StandaloneVotifierPlugin create() {
|
||||
Objects.requireNonNull(bind, "need an address to bind to");
|
||||
Objects.requireNonNull(receiver, "need a receiver for votes");
|
||||
return new StandaloneVotifierPlugin(keyMap, receiver, v1Key, bind);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.ibj.nuvotifier.platform.platform.LoggingAdapter;
|
||||
import io.ibj.nuvotifier.platform.platform.VotifierPlugin;
|
||||
import io.ibj.nuvotifier.platform.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.server.protocol.v1crypto.RSA;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.v1crypto.RSA;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PublicKey;
|
||||
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.net.server.protocol.VotifierProtocol1Decoder;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.VotifierProtocol1Decoder;
|
||||
import io.ibj.nuvotifier.platform.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.server.protocol.v1crypto.RSA;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.v1crypto.RSA;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.net.server.protocol.VotifierProtocol2Decoder;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.VotifierProtocol2Decoder;
|
||||
import io.ibj.nuvotifier.platform.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.server.protocol.VotifierProtocolDifferentiator;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.VotifierProtocolDifferentiator;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Changing the version of NuVotifier
|
||||
|
||||
So since I keep forgetting, here are the places which need to have their version
|
||||
numbers changed:
|
||||
|
||||
+ build.gradle
|
||||
+ com.vexsoftware.votifier.sponge.VotifierPlugin @Plugin
|
||||
+ com.vexsoftware.votifier.velocity.VotifierPlugin @Plugin
|
||||
|
||||
This is really annoying.
|
||||
@@ -0,0 +1,80 @@
|
||||
This document outlines various developer resources concerning NuVotifier.
|
||||
|
||||
# Contributing
|
||||
|
||||
NuVotifier is an open source GNU GPLv3 licensed project. We accept contributions
|
||||
through pull requests, and will make sure to credit you for your gracious
|
||||
contribution.
|
||||
|
||||
# Building NuVotifier
|
||||
|
||||
NuVotifier can be built by running the following: `./gradlew build` or
|
||||
`./gradlew.bat build` on Windows. The resultant universal jar is built and
|
||||
written to `universal/build/libs/nuvotifier-{version}.jar`.
|
||||
|
||||
The build directories can be cleaned instead using the `./gradlew clean` (and
|
||||
`./gradlew.bat build`) command.
|
||||
|
||||
# NuVotifier as a plugin dependency
|
||||
|
||||
ParallelBlock now maintains an independent Maven repository for free use by the
|
||||
community. Builds are automatically published by a CI operated by ParallelBlock.
|
||||
|
||||
## Maven
|
||||
|
||||
You can have your project depend on NuVotifier as a dependency through the
|
||||
following code snippets:
|
||||
|
||||
```
|
||||
<project>
|
||||
...
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>ibj-public</id>
|
||||
<name>Ichbinjoe Public Repository</name>
|
||||
<url>https://nexus.ibj.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
...
|
||||
</repositories>
|
||||
...
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-universal</artifactId>
|
||||
<version>...</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
...
|
||||
</dependencies>
|
||||
...
|
||||
</project>
|
||||
```
|
||||
|
||||
## Gradle
|
||||
|
||||
You can include NuVotifier into your gradle project using the following lines:
|
||||
|
||||
```
|
||||
...
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://nexus.ibj.io/repository/maven-public/'
|
||||
}
|
||||
...
|
||||
}
|
||||
...
|
||||
dependencies {
|
||||
compileOnly "com.vexsoftware:nuvotifier-universal:version"
|
||||
...
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
# Implementing NuVotifier v2 protocol in a server list
|
||||
|
||||
NuVotifier provides production ready reference implementations for interacting
|
||||
with NuVotifier servers. These libraries are available below:
|
||||
|
||||
- [NodeJS](https://github.com/NuVotifier/votifier2-js)
|
||||
- [PHP](https://github.com/NuVotifier/votifier2-php)
|
||||
- [Golang](https://github.com/minecrafter/go-votifier)
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# FAQ
|
||||
|
||||
Also know as, the page you go to when NuVotifier isn't doing what it is supposed to.
|
||||
|
||||
From maintaining NuVotifier for some time, I receive a lot of the same
|
||||
questions. I try to answer them the best I can in this document.
|
||||
|
||||
## I voted (or submitted a test vote) and nothing happened. Why isn't it working?
|
||||
|
||||
Votifier v1 isn't smart enough to recover from errors. Most server list votifier
|
||||
implementations are even stupid enough to _ignore_ connection errors and report
|
||||
the vote as a success. Thus when using Votifier v1, there is no way to be sure
|
||||
that a vote is ever received by the receiving server.
|
||||
|
||||
Usually when NuVotifier doesn't report anything in the logs, the voting website
|
||||
is unable to connect to NuVotifier in the first place. The following things are
|
||||
usually to blame:
|
||||
|
||||
+ NuVotifier errored out during startup - check your startup logs
|
||||
+ NuVotifier couldn't bind to the ip/port combination - again, check your start
|
||||
up logs. I have another section on NuVotifier failing to bind errors.
|
||||
+ NuVotifier is not bound to the correct IP - Check the bind ip parameter in the
|
||||
NuVotifier configuration. If that doesn't work, try `0.0.0.0` - this will tell
|
||||
NuVotifier to bind to ALL IPs it can.
|
||||
+ You entered the port in incorrectly - double check the NuVotifier port in the
|
||||
configuration.
|
||||
+ You have a firewall in the way - If you are running on Windows, try to
|
||||
temporarily disable the firewall. Linux has a couple of different of culprits,
|
||||
including `iptables` and `ufw`.
|
||||
|
||||
## I voted (or submitted a test vote) and I got an IOException in the console. What gives?
|
||||
|
||||
How to treat an IOException depends on which specific IOException you receive:
|
||||
|
||||
### Connection reset by peer
|
||||
|
||||
This means that the voting website 'closed' the TCP connection to NuVotifier
|
||||
before it could read the entire Vote message. NuVotifier _can't_ fix this,
|
||||
because this is an implementation issue with voting lists. If you are receiving
|
||||
this exception, report this to voting lists, not NuVotifier.
|
||||
|
||||
If you are coming from a server list from a report of this error, consider
|
||||
implementing the Votifier v2 protocol - it is more secure, easier to implement,
|
||||
easier to diagnose, and is more resilient to network issues like this. Some
|
||||
implementations for popular languages already exist including PHP, Node.js,
|
||||
Golang, and Java.
|
||||
|
||||
### Connection reset by host
|
||||
|
||||
This means that your computer's operating system closed the TCP connection to
|
||||
NuVotifier before it could read the entire Vote message. Usually this is due to
|
||||
a firewall - disable firewalls on your system and try again.
|
||||
|
||||
### Other IOExceptions
|
||||
|
||||
Other errors don't show up commonly enough - report an issue with your
|
||||
configurations and server logs.
|
||||
|
||||
## Which forwarding mode should I use?
|
||||
|
||||
NuVotifier comes with two baked in forwarding modes: proxy and pluginMessaging.
|
||||
Proxy mode should only ever be used when your network includes multiple
|
||||
BungeeCords. Otherwise, you should always use pluginMessaging - it is easier to
|
||||
set up and includes more forwarding mechanics.
|
||||
|
||||
## Bungeecord gets my vote, however my backend servers don't.
|
||||
|
||||
First, make sure you are using the correct forwarding mode for your setup - you
|
||||
could save yourself a lot of headaches by using pluginMessaging instead of proxy
|
||||
mode. If you are still having issues, see the following sections.
|
||||
|
||||
### PluginMessaging
|
||||
|
||||
PluginMessaging is a lot more straight forward than other forwarding methods. If
|
||||
it does not work, check the following things:
|
||||
|
||||
+ Is the plugin messaging channel the same everywhere? Make sure it is lower
|
||||
case and includes a colon! (Previous NuVotifier versions defaulted to an
|
||||
invalid channel before 1.13 was released.)
|
||||
+ Is the server you are having issues with either on the blacklist or not on the
|
||||
whitelist?
|
||||
|
||||
### Proxy
|
||||
|
||||
A lot more can go wrong when using proxy. A lot of people do not set it up
|
||||
correctly. If you are using proxy mode, make absolutely sure you actually need
|
||||
proxy mode. With that being said, here are some things you can check:
|
||||
|
||||
+ For each backend server, diagnose connection issues according to simply not
|
||||
getting votes - usually the server is unable to bind to a port, or there is
|
||||
another issue related to firewalls, ports, and that sort of thing.
|
||||
+ Ensure you are using the v2 Votifier token generated by each server. This is
|
||||
NOT the key found in the `rsa/` folder.
|
||||
+ Check ALL of your logs. One of them says something. It doesn't? You are lying.
|
||||
ONE OF THEM DOES. Check them again.
|
||||
|
||||
## Does NuVotifier support X?
|
||||
|
||||
The answer is yes if the original Votifier supported it or if the plugin says
|
||||
they support NuVotifier.
|
||||
|
||||
## I used /reload or /plugman on NuVotifier and now it doesn't work!
|
||||
|
||||
Reloads are not supported by NuVotifier. Restart your server, and use
|
||||
`/nvreload` in the console to reload the NuVotifier configuration.
|
||||
|
||||
## NuVotifier is lagging my server!
|
||||
|
||||
No its not. One of your vote listeners is.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Documentation
|
||||
|
||||
This directory holds documentation for NuVotifier.
|
||||
|
||||
# Usage Guide
|
||||
|
||||
The usage/configuration guide may be found [here](usage.md).
|
||||
|
||||
# Developer Guide
|
||||
|
||||
The developer guide for NuVotifier may be found [here](developer.md).
|
||||
|
||||
# Technical QA
|
||||
|
||||
Some assorted technical questions not important to most people are found
|
||||
[here](technical_qa.md).
|
||||
|
||||
# FAQ
|
||||
|
||||
You can find the answers to those common questions you have [here](faq.md).
|
||||
@@ -0,0 +1,78 @@
|
||||
This document outlines some of the technical reasons for a new NuVotifier.
|
||||
|
||||
# Why a new protocol?
|
||||
|
||||
The old Votifier protocol has three major flaws, which we feel are corrected in the new protocol.
|
||||
|
||||
* The data is encrypted (with RSA) but not hashed to verify its integrity. The new protocol utilizes HMAC with SHA256 (but no encryption, given that the data sent is eventually revealed in some form or another).
|
||||
* The canonical Votifier protocol is vulnerable to replay attacks.
|
||||
* The canonical Votifier is one key for all clients, which is weak. While NuVotifier still supports a default key (for the less paranoid), this ability can be disabled and force per-server keys.
|
||||
|
||||
# Is NuVotifier a drop-in replacement for Votifier?
|
||||
|
||||
Yes. NuVotifier is compatible with Votifier server lists and Votifier listeners, with some differences:
|
||||
|
||||
* The `Vote` object's setters are deprecated.
|
||||
* Support for standalone vote listeners (`.class` files) has been removed, as handling `VotifierEvent` is more flexible.
|
||||
|
||||
# Protocol Documentation
|
||||
|
||||
This documentation is for server lists that wish to add NuVotifier support.
|
||||
|
||||
There are two versions of the NuVotifier protocol.
|
||||
|
||||
## Handshake
|
||||
|
||||
A connection is made to the NuVotifier server by the server list, and immediately NuVotifier will send its version in the following packet:
|
||||
|
||||
"VOTIFIER <version> <challenge>"
|
||||
|
||||
The challenge will not be present in version 1.x servers.
|
||||
|
||||
## Protocol v2
|
||||
|
||||
NuVotifier expects a message composed of `0x733A` (16-bit big-endian integer) and the length of the following JSON message (as a 16-bit big-endian integer) sent plus the message.
|
||||
|
||||
The message is JSON-encoded containing the following data:
|
||||
|
||||
* `payload` contains the fully encoded vote. It is a JSON message containing the `serviceName` (string), `username` (string), `address` (string, but must be a valid IPv4 or IPv6 address), `timestamp` (a long with the time this vote was taken in _milliseconds_), and `challenge` (from the handshake). It is optional, but recommended to supply a `uuid` field with the player's UUID (with dashes, e.g. `652a2bc4-e8cd-405d-b7b6-98156ee2dc09`).
|
||||
* `signature` is a HMAC-SHA256 digest of the payload with a owner-provided key.
|
||||
|
||||
A response is given after the vote has been received and parsed, but not yet processed.
|
||||
|
||||
For your convenience, the NuVotifier team has provided client libraries in [JavaScript](https://github.com/NuVotifier/votifier2-js), [PHP](https://github.com/NuVotifier/votifier2-php) and [Go](https://github.com/minecrafter/go-votifier). If you are stuck, these libraries may help you.
|
||||
|
||||
## Protocol v1 (deprecated)
|
||||
|
||||
For better compatibility, NuVotifier supports the old Votifier protocol as well.
|
||||
|
||||
NuVotifier expects a 256 byte RSA encrypted block (the public key should be obtained by the NuVotifier user), with the following format:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>VOTE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>serviceName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>username</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>address</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>timeStamp</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
The first string of value "VOTE" is an opcode check to ensure that RSA was encoded and decoded properly, if this value is wrong then NuVotifier assumes that there was a problem with encryption and drops the connection. `serviceName` is the name of the top list service, `username` is the username (entered by the voter) of the person who voted, `address` is the IP address of the voter, and `timeStamp` is the time stamp of the vote. Each string is delimited by the newline character `\n` (byte value 10). The `space` block is the empty space that is left over, **the block must be exactly 256 bytes** regardless of how much information it holds.
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
This document outlines how to configure NuVotifier for a variety of different
|
||||
configurations. It should be used by server administrators looking to set up
|
||||
NuVotifier on their network.
|
||||
|
||||
# Key vs Token
|
||||
|
||||
This document uses the terms `token` and `key`. These are two separate terms,
|
||||
and it is important to understand the differences.
|
||||
|
||||
## Key
|
||||
|
||||
Keys in terms of NuVotifier refer to the old Votifier style RSA keys. They
|
||||
are typically distributed as a pair of files - a public and private key. When
|
||||
dealing with RSA keys, you can share your public key with server lists, but make
|
||||
sure to keep your private key safe with you. You can find these keys
|
||||
auto-generated under `plugins/(Nu)Votifier/rsa/`, where your public key is
|
||||
named `public.key`, while your private key is labeled `private.key`. Make sure
|
||||
not to separate the two, as they won't work together.
|
||||
|
||||
## Token
|
||||
|
||||
Tokens in terms of NuVotifier refer to a new short string of characters. This
|
||||
string of characters is secret, and should never be exposed to anyone but you or
|
||||
server lists which support NuVotifier voting (check with the server list
|
||||
first). Tokens are most commonly used internally within a network for use when
|
||||
using the proxy vote forwarding method.
|
||||
|
||||
# Single Server Configuration (Bukkit / Spigot / Sponge)
|
||||
|
||||
You will want to set up your server using these instructions when you only have
|
||||
one Minecraft server (Bukkit / Sponge). These are the most trivial installation
|
||||
steps.
|
||||
|
||||
## Installing the plugin on a server
|
||||
|
||||
To install NuVotifier, you should get a copy from one of the following
|
||||
locations:
|
||||
|
||||
+ [Github](https://github.com/parallelblock/NuVotifier/releases) - Official
|
||||
release location
|
||||
+ [SpigotMC](https://www.spigotmc.org/resources/nuvotifier.13449/) - SpigotMC
|
||||
release mirror
|
||||
+ [repo.parallelblock.com](https://repo.parallelblock.com/repository/public-raw/nuvotifier/nuvotifier.jar) - ParallelBlock CI 'hot off the presses' releases
|
||||
|
||||
The NuVotifier jar should then be placed within the `plugins/` folder of your
|
||||
Minecraft server. When you then start your server, NuVotifier will generate
|
||||
the necessary configuration and initial keys for you.
|
||||
|
||||
## Configuring the plugin
|
||||
|
||||
When running in a single server configuration, NuVotifier's default
|
||||
configuration works well. The only value which you may need to change is the
|
||||
`port` configuration value - this specifies the port on which NuVotifier
|
||||
listens to new votes. This port cannot be any port you are already using. For
|
||||
example, if Minecraft is running on the default port (25565), you cannot set
|
||||
`port` also to 25565. Make sure port is a number from 1-65535.
|
||||
|
||||
You can then add your server to server lists using the port you configured while
|
||||
also supplying your public key. At this point, you should be able to send a test
|
||||
vote to your server!
|
||||
|
||||
# Multi-server / Network configuration
|
||||
|
||||
You will want to use these instructions when you have more than one server and
|
||||
are using a Minecraft proxy (such as BungeeCord, LilyPad, or Velocity).
|
||||
|
||||
## Single-Proxy BungeeCord/Velocity Networks
|
||||
|
||||
When running a single-proxy network, NuVotifier is able to operate as both
|
||||
a vote receiver (emitting events for proxy based vote listeners) as well as
|
||||
a vote forwarder. NuVotifier's vote forwarding logic is also advanced enough
|
||||
to be used in a wide variety of ways.
|
||||
|
||||
### Installing the plugin on the servers
|
||||
|
||||
NuVotifier should be installed on each of the servers you wish to process votes
|
||||
on, as well as the proxy server. The proxy's NuVotifier server will act as the
|
||||
public facing Votifier instance, where as the Minecraft servers will instead
|
||||
receive votes forwarded from the proxy's NuVotifier.
|
||||
|
||||
Like the single server installation, you can download NuVotifier at any one of
|
||||
the following locations:
|
||||
|
||||
+ [Github](https://github.com/parallelblock/NuVotifier/releases) - Official
|
||||
release location
|
||||
+ [SpigotMC](https://www.spigotmc.org/resources/nuvotifier.13449/) - SpigotMC
|
||||
release mirror
|
||||
+ [repo.parallelblock.com](https://repo.parallelblock.com/repository/public-raw/nuvotifier/nuvotifier.jar) - ParallelBlock CI 'hot off the presses' releases
|
||||
|
||||
The plugin should then be placed in the `plugins/` directory of the Bungeecord
|
||||
server as well as each of the Minecraft servers.
|
||||
|
||||
### Vote Forwarding
|
||||
|
||||
The following sections describe how vote forwarding works. Vote forwarding is an
|
||||
advanced configurable operation, and requires a lot of configuration to get
|
||||
correct.
|
||||
|
||||
### Plugin messaging based forwarding
|
||||
|
||||
This section describes how to set up plugin messaging forwarding. This is
|
||||
recommended if you have just one proxy, however this may also work with
|
||||
some exceptions in multi-proxy networks.
|
||||
|
||||
First off, we should configure each of the Minecraft servers. Open each of the
|
||||
servers' NuVotifier configurations. Each one should have `method` under the
|
||||
`forwarding` configuration section set to `pluginMessaging`. Under the
|
||||
`pluginMessaging` configuration section, `channel` is set to `nuvotifier:votes`.
|
||||
Since 1.13, Spigot has decided that channels must now contain a colon. While
|
||||
this is a completely senseless change, it is easier to simply conform and change
|
||||
the channel name. This is a safe default (and is protected from injection by a
|
||||
hacked Minecraft client), but feel free to change it. If you choose to change
|
||||
it, make sure you change it everywhere all around your network!
|
||||
|
||||
Since we are receiving forwarded votes through plugin messaging and not through
|
||||
network ports, we should set `port` on each of the Minecraft servers to -1. This
|
||||
will disable NuVotifier's port, while still allowing it to receive forwarded
|
||||
plugin messaging votes. This will only work when using plugin messaging!
|
||||
|
||||
Since we have all of that configured, we can now move to the proxy's NuVotifier
|
||||
configuration. It is once again located under `plugins/NuVotifier/` directory
|
||||
(`config.yml` for BungeeCord, `config.toml` for Velocity). Like the single server,
|
||||
`port` should be set to an unbound port. The default `8192` works well, however
|
||||
you may have to change it depending on your hosting provider.
|
||||
|
||||
Since we are using plugin messaging based forwarding, lets set `method` under
|
||||
`forwarding` to `pluginMessaging`. Make sure that `channel` is the same channel
|
||||
that you configured your servers to listen to - if you didn't change this value,
|
||||
then the default will work.
|
||||
|
||||
NuVotifier saves when it can't immediately forward in a user defined cache. In
|
||||
almost all installations this should be `file`, however, some installations may
|
||||
instead want `none` or `memory`. If you don't know, the `cache` should be set to
|
||||
its default, `file`.
|
||||
|
||||
By default, NuVotifier will forward the vote to all of the connected Minecraft
|
||||
servers (all servers you can get to when typing /server). You can manually
|
||||
exclude some of these servers by first uncommenting `excludedServers`, then
|
||||
adding the server's name to the `excludedServers` list. After adding, NuVotifier
|
||||
will not send any votes to this server. If you only send the vote to the server
|
||||
the player joined, these servers are ignored.
|
||||
|
||||
NuVotifier can also selectively send the vote to only the server which the
|
||||
player has joined. This option can be turned on by setting
|
||||
`onlySendToJoinedServer` to true. When this option is on, the vote will attempt
|
||||
to go to the server which the player is currently on. If the player is currently
|
||||
not on a server (offline), then NuVotifier will then attempt to send the vote to
|
||||
the `joinedServerFallback`. If the vote cannot be sent to the
|
||||
`joinedServerFallback`, then the vote is saved in the cache until the vote can
|
||||
be sent to the `joinedServerFallback` server - note this will not send it to the
|
||||
next server the player joins! If you do not want a fallback server and would
|
||||
rather the vote be applied when the player next joins, set
|
||||
`joinedServerFallback` to `''`. This will cause NuVotifier to save the vote
|
||||
until the player joins again.
|
||||
|
||||
By default, votes are saved for 10 days in the cache. This time can be increased
|
||||
or decreased by changing the `cacheTime` field. This number can be as high or low
|
||||
as you want. It is measured in days within the cache. This is valid for both the
|
||||
`file` and `memory` cache.
|
||||
|
||||
Sometimes, caches can grow quite large and try to dump lots of votes back over the
|
||||
player's game connection. This is a problem, as the Bukkit server may think it is
|
||||
instead the player spamming packets! NuVotifier rate-limits the votes which are
|
||||
evicted per second over a plugin messaging channel using the `dumpRate` parameter.
|
||||
|
||||
### Proxy based vote forwarding
|
||||
|
||||
In more complex network situations, it may be required to instead use the proxy
|
||||
based vote forwarding. This configuration is less flexible and more difficult to
|
||||
set up properly - most people should use plugin messaging based forwarding.
|
||||
Proxy based forwarding does not support vote caching or advanced forwarding
|
||||
configurations. If you cannot use plugin messaging forwarding, then the
|
||||
following section will describe how to set up proxy based vote forwarding. If
|
||||
you still require advanced forwarding features or more advanced behaviors, it
|
||||
may be worthwhile to write your own plugin to perform the proper forwarding logic
|
||||
(you will know if you need to do this).
|
||||
|
||||
In proxy based vote forwarding, one of your proxy servers acts as the head vote
|
||||
receiver. This server will be the one you configure the voting websites to
|
||||
vote to. You can set up multiple vote receivers and load balance between them,
|
||||
however this configuration has limited benefit and is probably more work than it
|
||||
is worth. Instead of communicating through plugin messaging like the plugin
|
||||
messaging forwarding strategy, proxy forwarding instead acts like a voting
|
||||
website to each of the backend servers and duplicates the vote it receives to
|
||||
each of the backend servers it is configured with.
|
||||
|
||||
First, we will set up all of the Minecraft servers before setting up the head
|
||||
vote receiver. For each of the servers, NuVotifier should be configured to bind
|
||||
to an unused port. This port must be valid. If you have multiple Votifiers on
|
||||
the same IP/server, make sure that the port which you are using between
|
||||
NuVotifier instances are different. Remember the the port number as we will need
|
||||
it later to insert into the proxy NuVotifier configuration. In addition to the
|
||||
port number, you will also need the default token - this is the string auto
|
||||
generated by NuVotifier under the `default` key under `tokens`. Since we are using
|
||||
proxy forwarding, change `disable-v1-protocol` to true - we will only be using the
|
||||
new NuVotifier protocol within our network (vote receiver -> minecraft server).
|
||||
Once all configured, that is all we need to do. The rest of the defaults will work.
|
||||
|
||||
Now we will configure the vote receiving NuVotifier proxy server. The port
|
||||
should be configured as described many times above, being a unique port. Instead
|
||||
of setting `method` to `pluginMessaging` like above, we will set `method` to
|
||||
`proxy`. This will tell NuVotifier that we instead want to proxy the votes
|
||||
instead of forward them through plugin messaging channels.
|
||||
|
||||
The last step is to set up the proxy forwaring settings. Under the `proxy`
|
||||
section is a place to enter each server. Create a unique name for each of the
|
||||
server's you will be forwarding votes to. The default proxy NuVotifier
|
||||
configuration already has two Minecraft servers filled in - Hub and Hub2. You
|
||||
should replace these with your own servers - the names of the servers do not
|
||||
matter. Under each of these servers, we need to configure `address`, `port`, and
|
||||
`token`. `address` is the IP address of the Bukkit/Sponge server. This may be
|
||||
`localhost` or `127.0.0.1` if the Minecraft server is on the same physical
|
||||
server as the vote receiving NuVotifier server. `port` is the port of the
|
||||
Bukkit/Sponge NuVotifier server. `token` is the default token of the
|
||||
Bukkit/Sponge NuVotifier server. This can be found within its configuration,
|
||||
usually under the `token` section. Once the servers are configured, NuVotifier
|
||||
will forward all votes it receives to the servers configured under the `proxy`
|
||||
section.
|
||||
|
||||
# Debugging NuVotifier
|
||||
|
||||
NuVotifier can be difficult to set up in some configurations. If you find you
|
||||
don't know what is wrong, you may want to try setting `debug` in each of the
|
||||
server's NuVotifier configurations to `true`. This will increase logging within
|
||||
your console.
|
||||
|
||||
# Reloading NuVotifier
|
||||
|
||||
For Bukkit and Bungee, you may reload NuVotifier through the `/nvreload` command
|
||||
in your console only - this command will not work in-game by design. Make sure to
|
||||
ensure that it completed correctly before assuming NuVotifier works!
|
||||
|
||||
# Default Configurations
|
||||
|
||||
If you find you have messed up NuVotifier beyond repair, you can grab a default
|
||||
configuration from the following locations (depending on the server it is
|
||||
installed on):
|
||||
|
||||
+ [Bukkit](https://github.com/parallelblock/NuVotifier/blob/master/bukkit/src/main/resources/bukkitConfig.yml)
|
||||
+ [Sponge](https://github.com/parallelblock/NuVotifier/blob/master/sponge/src/main/resources/com/vexsoftware/votifier/sponge/spongeConfig.yml)
|
||||
+ [Bungeecord](https://github.com/parallelblock/NuVotifier/blob/master/bungeecord/src/main/resources/bungeeConfig.yml)
|
||||
|
||||
You can also regenerate this configuration by deleting the old configuration and
|
||||
restarting the server.
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
dependencies {
|
||||
api group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
dependencies {
|
||||
implementation group: "io.netty", name: "netty-handler", version: "4.1.32.Final"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
dependencies {
|
||||
api project(":model")
|
||||
|
||||
api project(":platform")
|
||||
|
||||
compile 'com.google.code.gson:gson:2.8.0'
|
||||
compile 'com.google.guava:guava:24.1-jre'
|
||||
|
||||
implementation group: "io.netty", name: "netty-handler", version: "4.1.32.Final"
|
||||
}
|
||||
+1
-3
@@ -1,10 +1,8 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
package io.ibj.nuvotifier.legacyserver;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class GsonInst {
|
||||
|
||||
public static final Gson gson = new GsonBuilder().create();
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
package io.ibj.nuvotifier.legacyserver;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
@@ -0,0 +1,99 @@
|
||||
package io.ibj.nuvotifier.legacyserver;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.VoteInboundHandler;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.VotifierGreetingHandler;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.VotifierProtocolDifferentiator;
|
||||
import io.ibj.nuvotifier.platform.LoggingAdapter;
|
||||
import io.ibj.nuvotifier.platform.V1KeyProvider;
|
||||
import io.ibj.nuvotifier.platform.V2TokenProvider;
|
||||
import io.ibj.nuvotifier.platform.VoteHandler;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOutboundInvoker;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.util.concurrent.DefaultPromise;
|
||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
||||
import io.netty.util.concurrent.PromiseCombiner;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class VotifierServer implements AutoCloseable {
|
||||
private NioEventLoopGroup bossLoopGroup;
|
||||
private NioEventLoopGroup eventLoopGroup;
|
||||
|
||||
private Set<Channel> serverChannels;
|
||||
|
||||
private final List<InetSocketAddress> bindAddresses;
|
||||
private final V1KeyProvider v1KeyProvider;
|
||||
private final V2TokenProvider v2TokenProvider;
|
||||
private final LoggingAdapter loggingAdapter;
|
||||
private final boolean testMode;
|
||||
private final VoteHandler voteHandler;
|
||||
|
||||
VotifierServer(VotifierServerBuilder builder) {
|
||||
this.bindAddresses = builder.bindAddresses;
|
||||
this.v1KeyProvider = builder.v1KeyProvider;
|
||||
this.v2TokenProvider = builder.v2TokenProvider;
|
||||
this.loggingAdapter = builder.loggingAdapter;
|
||||
this.testMode = builder.testMode;
|
||||
this.voteHandler = builder.voteHandler;
|
||||
}
|
||||
|
||||
public void start(Consumer<Throwable> error) {
|
||||
this.bossLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO boss").build());
|
||||
this.eventLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO worker").build());
|
||||
|
||||
Objects.requireNonNull(error, "error");
|
||||
ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(bossLoopGroup, eventLoopGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(v1KeyProvider, v2TokenProvider, testMode));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(voteHandler));
|
||||
}
|
||||
});
|
||||
|
||||
ChannelFutureListener listener = future -> {
|
||||
Channel serverChannel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
serverChannels.add(serverChannel);
|
||||
loggingAdapter.info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
|
||||
error.accept(null);
|
||||
} else {
|
||||
loggingAdapter.error("Votifier was not able to bind to " + serverChannel.localAddress().toString(), future.cause());
|
||||
error.accept(future.cause());
|
||||
}
|
||||
};
|
||||
|
||||
for (InetSocketAddress socketAddress : this.bindAddresses) {
|
||||
bootstrap.bind(socketAddress).addListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (!serverChannels.isEmpty()) {
|
||||
PromiseCombiner closePromises = new PromiseCombiner();
|
||||
serverChannels.stream().map(ChannelOutboundInvoker::close).forEach(closePromises::add);
|
||||
DefaultPromise<Void> closesDone = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
|
||||
closePromises.finish(closesDone);
|
||||
closesDone.awaitUninterruptibly();
|
||||
}
|
||||
|
||||
eventLoopGroup.shutdownGracefully();
|
||||
bossLoopGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.ibj.nuvotifier.legacyserver;
|
||||
|
||||
import io.ibj.nuvotifier.platform.LoggingAdapter;
|
||||
import io.ibj.nuvotifier.platform.V1KeyProvider;
|
||||
import io.ibj.nuvotifier.platform.V2TokenProvider;
|
||||
import io.ibj.nuvotifier.platform.VoteHandler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class VotifierServerBuilder {
|
||||
// Which addresses to bind to? We support multiple now!
|
||||
List<InetSocketAddress> bindAddresses = new ArrayList<>();
|
||||
|
||||
// Provides the server the ability to decode v1 keys. Defaults to
|
||||
V1KeyProvider v1KeyProvider = null;
|
||||
|
||||
// Provides the server the ability to validate v2 token payloads
|
||||
V2TokenProvider v2TokenProvider = null;
|
||||
|
||||
// Logging adapter which will be used to communicate various logs
|
||||
LoggingAdapter loggingAdapter = null;
|
||||
|
||||
// Whether not the server is in 'test mode'. Spoiler: unless you are testing, its not.
|
||||
boolean testMode = false;
|
||||
|
||||
// Handler to invoke when a vote is sent.
|
||||
VoteHandler voteHandler = null;
|
||||
|
||||
public VotifierServerBuilder addBindAddress(InetSocketAddress bindAddress) {
|
||||
this.bindAddresses.add(bindAddress);
|
||||
return this;
|
||||
}
|
||||
|
||||
public VotifierServerBuilder setV1KeyProvider(V1KeyProvider v1KeyProvider) {
|
||||
this.v1KeyProvider = v1KeyProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VotifierServerBuilder setV2TokenProvider(V2TokenProvider v2TokenProvider) {
|
||||
this.v2TokenProvider = v2TokenProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VotifierServerBuilder setLoggingAdapter(LoggingAdapter loggingAdapter) {
|
||||
this.loggingAdapter = loggingAdapter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VotifierServerBuilder setTestMode(boolean testMode) {
|
||||
this.testMode = testMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VotifierServerBuilder setVoteHandler(VoteHandler voteHandler) {
|
||||
this.voteHandler = voteHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public VotifierServer build() {
|
||||
return new VotifierServer(this);
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
package com.vexsoftware.votifier.net.server;
|
||||
package io.ibj.nuvotifier.legacyserver;
|
||||
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
public class VotifierSession {
|
||||
+8
-8
@@ -1,15 +1,15 @@
|
||||
package com.vexsoftware.votifier.net.server.protocol;
|
||||
package io.ibj.nuvotifier.legacyserver.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.ibj.nuvotifier.legacyserver.GsonInst;
|
||||
import io.ibj.nuvotifier.legacyserver.VotifierSession;
|
||||
import io.ibj.nuvotifier.model.NuVote;
|
||||
import io.ibj.nuvotifier.platform.VoteHandler;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
|
||||
public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
public class VoteInboundHandler extends SimpleChannelInboundHandler<NuVote> {
|
||||
private final VoteHandler handler;
|
||||
|
||||
public VoteInboundHandler(VoteHandler handler) {
|
||||
@@ -17,10 +17,10 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, final Vote vote) throws Exception {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, final NuVote vote) throws Exception {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
handler.onVoteReceived(vote, session.getVersion(), ctx.channel().remoteAddress().toString());
|
||||
handler.onVote(vote);
|
||||
session.completeVote();
|
||||
|
||||
if (session.getVersion() == VotifierSession.ProtocolVersion.ONE) {
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.net.server.protocol;
|
||||
package io.ibj.nuvotifier.legacyserver.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import io.ibj.nuvotifier.legacyserver.VotifierSession;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
+13
-8
@@ -1,8 +1,8 @@
|
||||
package com.vexsoftware.votifier.net.server.protocol;
|
||||
package io.ibj.nuvotifier.legacyserver.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.server.protocol.v1crypto.RSA;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.v1crypto.RSA;
|
||||
import io.ibj.nuvotifier.model.NuVote;
|
||||
import io.ibj.nuvotifier.platform.V1KeyProvider;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
@@ -15,6 +15,13 @@ import java.util.List;
|
||||
* Decodes original protocol votes.
|
||||
*/
|
||||
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
|
||||
|
||||
private final V1KeyProvider keyProvider;
|
||||
|
||||
public VotifierProtocol1Decoder(V1KeyProvider keyProvider) {
|
||||
this.keyProvider = keyProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
if (buf.readableBytes() < 256) {
|
||||
@@ -26,10 +33,8 @@ public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
|
||||
// "Drain" the whole buffer
|
||||
buf.readerIndex(buf.readableBytes());
|
||||
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
|
||||
try {
|
||||
block = RSA.decrypt(block, plugin.getProtocolV1Key().getPrivate());
|
||||
block = RSA.decrypt(block, keyProvider.getPrivateKey());
|
||||
} catch (Exception e) {
|
||||
throw new CorruptedFrameException("Could not decrypt data (is your key correct?)", e);
|
||||
}
|
||||
@@ -46,7 +51,7 @@ public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
// Create the vote.
|
||||
Vote vote = new Vote(split[1], split[2], split[3], split[4]);
|
||||
NuVote vote = NuVote.createLegacy(split[1], split[2], split[3], split[4]);
|
||||
list.add(vote);
|
||||
|
||||
// We are done, remove ourselves. Why? Sometimes, we will decode multiple vote messages.
|
||||
+19
-16
@@ -1,10 +1,10 @@
|
||||
package com.vexsoftware.votifier.net.server.protocol;
|
||||
package io.ibj.nuvotifier.legacyserver.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import io.ibj.nuvotifier.legacyserver.GsonInst;
|
||||
import io.ibj.nuvotifier.legacyserver.VotifierSession;
|
||||
import io.ibj.nuvotifier.platform.V2TokenProvider;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
@@ -23,6 +23,12 @@ import java.util.UUID;
|
||||
public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
private final V2TokenProvider tokenProvider;
|
||||
|
||||
public VotifierProtocol2Decoder(V2TokenProvider tokenProvider) {
|
||||
this.tokenProvider = tokenProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, String s, List<Object> list) throws Exception {
|
||||
JsonObject voteMessage = GsonInst.gson.fromJson(s, JsonObject.class);
|
||||
@@ -37,16 +43,9 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
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.get("serviceName").getAsString());
|
||||
|
||||
if (key == null) {
|
||||
key = plugin.getTokens().get("default");
|
||||
if (key == null) {
|
||||
throw new RuntimeException("Unknown service '" + votePayload.get("serviceName").getAsString() + "'");
|
||||
}
|
||||
}
|
||||
String serviceName = votePayload.get("serviceName").getAsString();
|
||||
// Get the key for the given service.
|
||||
Key key = tokenProvider.getKeyForService(serviceName);
|
||||
|
||||
// Verify signature.
|
||||
String sigHash = voteMessage.get("signature").getAsString();
|
||||
@@ -61,12 +60,16 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
UUID.fromString(votePayload.get("uuid").getAsString());
|
||||
}
|
||||
|
||||
if (votePayload.get("username").getAsString().length() > 16) {
|
||||
String username = votePayload.get("username").getAsString();
|
||||
if (username.length() > 16) {
|
||||
throw new CorruptedFrameException("Username too long");
|
||||
}
|
||||
|
||||
String address = votePayload.get("address").getAsString();
|
||||
long timestamp = votePayload.get("timestamp").getAsLong();
|
||||
|
||||
// Create the vote.
|
||||
Vote vote = new Vote(votePayload);
|
||||
Vote vote = new Vote(serviceName, username, address, Long.toString(timestamp));
|
||||
list.add(vote);
|
||||
|
||||
ctx.pipeline().remove(this);
|
||||
+17
-8
@@ -1,6 +1,8 @@
|
||||
package com.vexsoftware.votifier.net.server.protocol;
|
||||
package io.ibj.nuvotifier.legacyserver.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import io.ibj.nuvotifier.legacyserver.VotifierSession;
|
||||
import io.ibj.nuvotifier.platform.V1KeyProvider;
|
||||
import io.ibj.nuvotifier.platform.V2TokenProvider;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
@@ -18,11 +20,14 @@ import java.util.List;
|
||||
public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
private static final short PROTOCOL_2_MAGIC = 0x733A;
|
||||
private final boolean testMode;
|
||||
private final boolean allowv1;
|
||||
|
||||
public VotifierProtocolDifferentiator(boolean testMode, boolean allowv1) {
|
||||
private final V1KeyProvider v1;
|
||||
private final V2TokenProvider v2;
|
||||
|
||||
public VotifierProtocolDifferentiator(V1KeyProvider v1, V2TokenProvider v2, boolean testMode) {
|
||||
this.testMode = testMode;
|
||||
this.allowv1 = allowv1;
|
||||
this.v1 = v1;
|
||||
this.v2 = v2;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,24 +48,28 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
if (readMagic == PROTOCOL_2_MAGIC) {
|
||||
if (v2 == null) {
|
||||
throw new CorruptedFrameException("This server only accepts v1 Votifier packets.");
|
||||
}
|
||||
|
||||
// Short 0x733A + Message = Protocol v2 Vote
|
||||
session.setVersion(VotifierSession.ProtocolVersion.TWO);
|
||||
|
||||
if (!testMode) {
|
||||
ctx.pipeline().addAfter("protocolDifferentiator", "protocol2LengthDecoder", new LengthFieldBasedFrameDecoder(1024, 2, 2, 0, 4));
|
||||
ctx.pipeline().addAfter("protocol2LengthDecoder", "protocol2StringDecoder", new StringDecoder(StandardCharsets.UTF_8));
|
||||
ctx.pipeline().addAfter("protocol2StringDecoder", "protocol2VoteDecoder", new VotifierProtocol2Decoder());
|
||||
ctx.pipeline().addAfter("protocol2StringDecoder", "protocol2VoteDecoder", new VotifierProtocol2Decoder(v2));
|
||||
ctx.pipeline().addAfter("protocol2VoteDecoder", "protocol2StringEncoder", new StringEncoder(StandardCharsets.UTF_8));
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
} else {
|
||||
if (!allowv1) {
|
||||
if (v1 == null) {
|
||||
throw new CorruptedFrameException("This server only accepts well-formed Votifier v2 packets.");
|
||||
}
|
||||
// Probably Protocol v1 Vote Message
|
||||
session.setVersion(VotifierSession.ProtocolVersion.ONE);
|
||||
if (!testMode) {
|
||||
ctx.pipeline().addAfter("protocolDifferentiator", "protocol1Handler", new VotifierProtocol1Decoder());
|
||||
ctx.pipeline().addAfter("protocolDifferentiator", "protocol1Handler", new VotifierProtocol1Decoder(v1));
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.vexsoftware.votifier.net.server.protocol.v1crypto;
|
||||
package io.ibj.nuvotifier.legacyserver.protocol.v1crypto;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.security.PrivateKey;
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.vexsoftware.votifier.net.server.protocol.v1crypto;
|
||||
package io.ibj.nuvotifier.legacyserver.protocol.v1crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.vexsoftware.votifier.net.server.protocol.v1crypto;
|
||||
package io.ibj.nuvotifier.legacyserver.protocol.v1crypto;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
@@ -0,0 +1,3 @@
|
||||
# model
|
||||
|
||||
This module contains the 'model' for Votifier. It includes the legacy 'Vote' object as well as a 'NuVote' object which plugins which require extended information should depend upon.
|
||||
Whitespace-only changes.
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Vex Software LLC
|
||||
* This file is part of Votifier.
|
||||
*
|
||||
* Votifier is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Votifier is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.vexsoftware.votifier.model;
|
||||
|
||||
/**
|
||||
* A model for a vote.
|
||||
*
|
||||
* @author Blake Beaupain
|
||||
*/
|
||||
public class Vote {
|
||||
|
||||
/** The name of the vote service. */
|
||||
private final String serviceName;
|
||||
|
||||
/** The username of the voter. */
|
||||
private final String username;
|
||||
|
||||
/** The address of the voter. */
|
||||
private final String address;
|
||||
|
||||
/** The date and time of the vote. */
|
||||
private final String timeStamp;
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp) {
|
||||
this.serviceName = serviceName;
|
||||
this.username = username;
|
||||
this.address = address;
|
||||
this.timeStamp = timeStamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vote (from:" + serviceName + " username:" + username
|
||||
+ " address:" + address + " timeStamp:" + timeStamp + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the serviceName.
|
||||
*
|
||||
* @return The serviceName
|
||||
*/
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the username.
|
||||
*
|
||||
* @return The username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the address.
|
||||
*
|
||||
* @return The address
|
||||
*/
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time stamp.
|
||||
*
|
||||
* @return The time stamp
|
||||
*/
|
||||
public String getTimeStamp() {
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ibj.nuvotifier.model;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
/**
|
||||
* This is the NuVote object. It extends the old 'Vote' object. The old 'Vote' object is frozen - i.e. no changes will be
|
||||
* made to it. API additions to Vote happen in this file.
|
||||
*/
|
||||
public class NuVote extends Vote {
|
||||
private NuVote(String serviceName, String username, String address, String timeStamp) {
|
||||
super(serviceName, username, address, timeStamp);
|
||||
}
|
||||
|
||||
public static NuVote createLegacy(String serviceName, String username, String address, String timeStamp) {
|
||||
return new NuVote(serviceName, username, address, timeStamp);
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
package io.ibj.nuvotifier.multiplatform;
|
||||
|
||||
public interface BackendServer {
|
||||
String getName();
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
package io.ibj.nuvotifier.multiplatform;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ProxyVotifierPlugin extends VotifierPlugin {
|
||||
public interface ProxyVotifierPlugin {
|
||||
Collection<BackendServer> getAllBackendServers();
|
||||
|
||||
Optional<BackendServer> getServer(String name);
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies {
|
||||
api project(":model")
|
||||
}
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
package io.ibj.nuvotifier.platform;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
package io.ibj.nuvotifier.platform;
|
||||
|
||||
public interface LoggingAdapter {
|
||||
void error(String s);
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.ibj.nuvotifier.platform;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
|
||||
public interface V1KeyProvider {
|
||||
PrivateKey getPrivateKey();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.ibj.nuvotifier.platform;
|
||||
|
||||
import java.security.Key;
|
||||
|
||||
public interface V2TokenProvider {
|
||||
Key getKeyForService(String serviceName);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ibj.nuvotifier.platform;
|
||||
|
||||
import io.ibj.nuvotifier.model.NuVote;
|
||||
|
||||
public interface VoteHandler {
|
||||
void onVote(NuVote vote);
|
||||
|
||||
default void onError(Throwable throwable, boolean voteAlreadyCompleted, String remoteAddress) {
|
||||
throw new RuntimeException("Unimplemented onError handler");
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -1,6 +1,4 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
package io.ibj.nuvotifier.platform.scheduler;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -10,7 +8,9 @@ public class ScheduledExecutorServiceVotifierScheduler implements VotifierSchedu
|
||||
private final ScheduledExecutorService service;
|
||||
|
||||
public ScheduledExecutorServiceVotifierScheduler(ScheduledExecutorService service) {
|
||||
this.service = Preconditions.checkNotNull(service, "service");
|
||||
if (service == null)
|
||||
throw new NullPointerException("Service is null");
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
package io.ibj.nuvotifier.platform.scheduler;
|
||||
|
||||
public interface ScheduledVotifierTask {
|
||||
void cancel();
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
package io.ibj.nuvotifier.platform.scheduler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// This file contains special build instructions specifically for the ParallelBlock CI in order to instruct it how to
|
||||
// build the project. Usually, this shouldn't be interacted with by a human.
|
||||
|
||||
import com.mashape.unirest.http.*
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.mashape.unirest:unirest-java:1.4.9'
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: "maven-publish"
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name 'ibj'
|
||||
if (project.version.endsWith("-SNAPSHOT"))
|
||||
url "$System.env.SNAPSHOT_REPO"
|
||||
else
|
||||
url "$System.env.RELEASE_REPO"
|
||||
credentials {
|
||||
username "$System.env.REPO_USR"
|
||||
password "$System.env.REPO_PSW"
|
||||
}
|
||||
}
|
||||
}
|
||||
publications {
|
||||
versioned(MavenPublication) {
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (System.env.LATEST_BUILD) {
|
||||
task publishLatestToIbjBlockRaw {
|
||||
inputs.files(configurations.archives.artifacts.files)
|
||||
outputs.upToDateWhen {false}
|
||||
doLast {
|
||||
configurations.archives.artifacts.each {
|
||||
def resp = Unirest.put("$System.env.RAW_UPLOAD_PATH/${it.name}.${it.extension}")
|
||||
.basicAuth("$System.env.REPO_USR", "$System.env.REPO_PSW")
|
||||
.body(it.file.bytes).asString()
|
||||
assert resp.status >= 200 && resp.status <= 299
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-10
@@ -1,20 +1,17 @@
|
||||
rootProject.name = 'nuvotifier'
|
||||
|
||||
include 'api'
|
||||
findProject(':api')?.name = 'nuvotifier-api'
|
||||
// The model for a 'Vote'.
|
||||
include 'model'
|
||||
|
||||
include 'common'
|
||||
findProject(':common')?.name = 'nuvotifier-common'
|
||||
// The 'platform' api.
|
||||
include 'platform'
|
||||
include 'legacyserver'
|
||||
|
||||
include 'bukkit'
|
||||
findProject(':bukkit')?.name = 'nuvotifier-bukkit'
|
||||
include 'bungeecord'
|
||||
findProject(':bungeecord')?.name = 'nuvotifier-bungeecord'
|
||||
include 'common'
|
||||
include 'sponge'
|
||||
findProject(':sponge')?.name = 'nuvotifier-sponge'
|
||||
|
||||
include 'universal'
|
||||
findProject(':universal')?.name = 'nuvotifier-universal'
|
||||
include 'velocity'
|
||||
findProject(':velocity')?.name = 'nuvotifier-velocity'
|
||||
include 'multiplatform'
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
dependencies {
|
||||
compileOnly group: "org.spongepowered", name: "spongeapi", version: "7.1.0"
|
||||
annotationProcessor group: "org.spongepowered", name: "spongeapi", version: "7.1.0"
|
||||
compile project(":nuvotifier-common")
|
||||
compile project(":common")
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.sponge;
|
||||
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import io.ibj.nuvotifier.platform.platform.LoggingAdapter;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class SLF4JLogger implements LoggingAdapter {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.vexsoftware.votifier.sponge;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.ibj.nuvotifier.platform.platform.scheduler.ScheduledVotifierTask;
|
||||
import io.ibj.nuvotifier.platform.platform.scheduler.VotifierScheduler;
|
||||
import org.spongepowered.api.Sponge;
|
||||
import org.spongepowered.api.scheduler.Task;
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.server.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.server.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.server.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.server.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.v1crypto.RSAIO;
|
||||
import io.ibj.nuvotifier.legacyserver.protocol.v1crypto.RSAKeygen;
|
||||
import io.ibj.nuvotifier.platform.platform.LoggingAdapter;
|
||||
import io.ibj.nuvotifier.platform.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.sponge.cmd.NVReloadCmd;
|
||||
import com.vexsoftware.votifier.sponge.cmd.TestVoteCmd;
|
||||
import com.vexsoftware.votifier.sponge.config.ConfigLoader;
|
||||
@@ -36,7 +36,7 @@ import java.util.Map;
|
||||
|
||||
@Plugin(id = "nuvotifier", name = "NuVotifier", version = "3.0.0-SNAPSHOT", authors = "Ichbinjoe",
|
||||
description = "Safe, smart, and secure Votifier server plugin")
|
||||
public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.platform.VotifierPlugin, ForwardedVoteListener {
|
||||
public class VotifierPlugin implements VoteHandler, io.ibj.nuvotifier.platform.platform.VotifierPlugin, ForwardedVoteListener {
|
||||
|
||||
@Inject
|
||||
public Logger logger;
|
||||
|
||||
@@ -17,10 +17,10 @@ buildscript {
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
dependencies {
|
||||
compile project(":nuvotifier-bukkit")
|
||||
compile project(":nuvotifier-bungeecord")
|
||||
compile project(":nuvotifier-sponge")
|
||||
compile project(":nuvotifier-velocity")
|
||||
compile project(":bukkit")
|
||||
compile project(":bungeecord")
|
||||
compile project(":sponge")
|
||||
compile project(":velocity")
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
dependencies {
|
||||
compileOnly group: "com.velocitypowered", name: "velocity-api", version: "1.0-SNAPSHOT"
|
||||
annotationProcessor group: "com.velocitypowered", name: "velocity-api", version: "1.0-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
compile project(":common")
|
||||
}
|
||||
+1
-1
@@ -8,7 +8,7 @@ import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import io.ibj.nuvotifier.platform.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
|
||||
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user