A ton of javadocs and small fixes in preparation for rewriting bridge system and eventual release.

This commit is contained in:
md_5 2012-10-12 22:20:37 +11:00
parent c6749726c5
commit aa2710c573
29 changed files with 316 additions and 249 deletions

View File

@ -11,6 +11,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.logging.Level;
import static net.md_5.bungee.Logger.$; import static net.md_5.bungee.Logger.$;
import net.md_5.bungee.command.Command; import net.md_5.bungee.command.Command;
import net.md_5.bungee.command.CommandEnd; import net.md_5.bungee.command.CommandEnd;
@ -20,6 +21,9 @@ import net.md_5.bungee.command.CommandServer;
import net.md_5.bungee.command.ConsoleCommandSender; import net.md_5.bungee.command.ConsoleCommandSender;
import net.md_5.bungee.plugin.JavaPluginManager; import net.md_5.bungee.plugin.JavaPluginManager;
/**
* Main BungeeCord proxy class.
*/
public class BungeeCord { public class BungeeCord {
/** /**
@ -69,6 +73,12 @@ public class BungeeCord {
commandMap.put("server", new CommandServer()); commandMap.put("server", new CommandServer());
} }
/**
* Starts a new instance of BungeeCord.
*
* @param args command line arguments, currently none are used
* @throws IOException when the server cannot be started
*/
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
instance = new BungeeCord(); instance = new BungeeCord();
$().info("Enabled BungeeCord version " + instance.version); $().info("Enabled BungeeCord version " + instance.version);
@ -86,26 +96,38 @@ public class BungeeCord {
} }
} }
/**
* Dispatch a command by formatting the arguments and then executing it.
*
* @param commandLine the entire command and arguments string
* @param sender which executed the command
* @return whether the command was handled or not.
*/
public boolean dispatchCommand(String commandLine, CommandSender sender) { public boolean dispatchCommand(String commandLine, CommandSender sender) {
String[] split = commandLine.trim().split(" "); String[] split = commandLine.trim().split(" ");
String commandName = split[0].toLowerCase(); String commandName = split[0].toLowerCase();
if (commandMap.containsKey(commandName)) { Command command = commandMap.get(commandName);
if (command != null) {
String[] args = Arrays.copyOfRange(split, 1, split.length); String[] args = Arrays.copyOfRange(split, 1, split.length);
Command c = commandMap.get(commandName);
try { try {
c.execute(sender, args); command.execute(sender, args);
} catch (Exception ex) { } catch (Exception ex) {
sender.sendMessage(ChatColor.RED + "An error occurred while executing this command!"); sender.sendMessage(ChatColor.RED + "An error occurred while executing this command!");
System.err.println("----------------------- [Start of command error] -----------------------"); $().severe("----------------------- [Start of command error] -----------------------");
ex.printStackTrace(); $().log(Level.SEVERE, "", ex);
System.err.println("----------------------- [End of command error] -----------------------"); $().severe("----------------------- [End of command error] -----------------------");
} }
return true;
} else {
return false;
} }
return command != null;
} }
/**
* Start this proxy instance by loading the configuration, plugins and
* starting the connect thread.
*
* @throws IOException
*/
public void start() throws IOException { public void start() throws IOException {
config.load(); config.load();
isRunning = true; isRunning = true;
@ -120,6 +142,10 @@ public class BungeeCord {
$().info("Listening on " + addr); $().info("Listening on " + addr);
} }
/**
* Destroy this proxy instance cleanly by kicking all users, saving the
* configuration and closing all sockets.
*/
public void stop() { public void stop() {
this.isRunning = false; this.isRunning = false;
$().info("Disabling plugin"); $().info("Disabling plugin");
@ -151,6 +177,13 @@ public class BungeeCord {
$().info("Thank you and goodbye"); $().info("Thank you and goodbye");
} }
/**
* Miscellaneous method to set options on a socket based on those in the
* configuration.
*
* @param socket to set the options on
* @throws IOException when the underlying set methods thrown an exception
*/
public void setSocketOptions(Socket socket) throws IOException { public void setSocketOptions(Socket socket) throws IOException {
socket.setSoTimeout(config.timeout); socket.setSoTimeout(config.timeout);
socket.setTrafficClass(0x18); socket.setTrafficClass(0x18);

View File

@ -1,133 +1,116 @@
package net.md_5.bungee; package net.md_5.bungee;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* All supported color values for chat * Simplistic enumeration of all supported color values for chat.
*/ */
public enum ChatColor { public enum ChatColor {
/** /**
* Represents black * Represents black.
*/ */
BLACK('0', 0x00), BLACK('0'),
/** /**
* Represents dark blue * Represents dark blue.
*/ */
DARK_BLUE('1', 0x1), DARK_BLUE('1'),
/** /**
* Represents dark green * Represents dark green.
*/ */
DARK_GREEN('2', 0x2), DARK_GREEN('2'),
/** /**
* Represents dark blue (aqua) * Represents dark blue (aqua).
*/ */
DARK_AQUA('3', 0x3), DARK_AQUA('3'),
/** /**
* Represents dark red * Represents dark red.
*/ */
DARK_RED('4', 0x4), DARK_RED('4'),
/** /**
* Represents dark purple * Represents dark purple.
*/ */
DARK_PURPLE('5', 0x5), DARK_PURPLE('5'),
/** /**
* Represents gold * Represents gold.
*/ */
GOLD('6', 0x6), GOLD('6'),
/** /**
* Represents gray * Represents gray.
*/ */
GRAY('7', 0x7), GRAY('7'),
/** /**
* Represents dark gray * Represents dark gray.
*/ */
DARK_GRAY('8', 0x8), DARK_GRAY('8'),
/** /**
* Represents blue * Represents blue.
*/ */
BLUE('9', 0x9), BLUE('9'),
/** /**
* Represents green * Represents green.
*/ */
GREEN('a', 0xA), GREEN('a'),
/** /**
* Represents aqua * Represents aqua.
*/ */
AQUA('b', 0xB), AQUA('b'),
/** /**
* Represents red * Represents red.
*/ */
RED('c', 0xC), RED('c'),
/** /**
* Represents light purple * Represents light purple.
*/ */
LIGHT_PURPLE('d', 0xD), LIGHT_PURPLE('d'),
/** /**
* Represents yellow * Represents yellow.
*/ */
YELLOW('e', 0xE), YELLOW('e'),
/** /**
* Represents white * Represents white.
*/ */
WHITE('f', 0xF), WHITE('f'),
/** /**
* Represents magical characters that change around randomly * Represents magical characters that change around randomly.
*/ */
MAGIC('k', 0x10, true), MAGIC('k'),
/** /**
* Makes the text bold. * Makes the text bold.
*/ */
BOLD('l', 0x11, true), BOLD('l'),
/** /**
* Makes a line appear through the text. * Makes a line appear through the text.
*/ */
STRIKETHROUGH('m', 0x12, true), STRIKETHROUGH('m'),
/** /**
* Makes the text appear underlined. * Makes the text appear underlined.
*/ */
UNDERLINE('n', 0x13, true), UNDERLINE('n'),
/** /**
* Makes the text italic. * Makes the text italic.
*/ */
ITALIC('o', 0x14, true), ITALIC('o'),
/** /**
* Resets all previous chat colors or formats. * Resets all previous chat colors or formats.
*/ */
RESET('r', 0x15); RESET('r');
/** /**
* The special character which prefixes all chat colour codes. Use this if * The special character which prefixes all chat colour codes. Use this if
* you need to dynamically convert colour codes from your custom format. * you need to dynamically convert colour codes from your custom format.
*/ */
public static final char COLOR_CHAR = '\u00A7'; public static final char COLOR_CHAR = '\u00A7';
private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + String.valueOf(COLOR_CHAR) + "[0-9A-FK-OR]");
private final int intCode;
private final char code;
private final boolean isFormat;
private final String toString;
private final static Map<Integer, ChatColor> BY_ID = Maps.newHashMap();
private final static Map<Character, ChatColor> BY_CHAR = Maps.newHashMap();
private ChatColor(char code, int intCode) {
this(code, intCode, false);
}
private ChatColor(char code, int intCode, boolean isFormat) {
this.code = code;
this.intCode = intCode;
this.isFormat = isFormat;
this.toString = new String(new char[]{COLOR_CHAR, code});
}
/** /**
* Gets the char value associated with this color * Pattern to remove all colour codes.
*
* @return A char value of this color code
*/ */
public char getChar() { private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + String.valueOf(COLOR_CHAR) + "[0-9A-FK-OR]");
return code; /**
* This colour's colour char prefixed by the {@link #COLOR_CHAR}.
*/
private final String toString;
private ChatColor(char code) {
this.toString = new String(new char[]{COLOR_CHAR, code});
} }
@Override @Override
@ -135,31 +118,6 @@ public enum ChatColor {
return toString; return toString;
} }
/**
* Checks if this code is a format code as opposed to a color code.
*/
public boolean isFormat() {
return isFormat;
}
/**
* Checks if this code is a color code as opposed to a format code.
*/
public boolean isColor() {
return !isFormat && this != RESET;
}
/**
* Gets the color represented by the specified color code
*
* @param code Code to check
* @return Associative {@link org.bukkit.ChatColor} with the given code, or
* null if it doesn't exist
*/
public static ChatColor getByChar(char code) {
return BY_CHAR.get(code);
}
/** /**
* Strips the given message of all color codes * Strips the given message of all color codes
* *
@ -173,64 +131,4 @@ public enum ChatColor {
return STRIP_COLOR_PATTERN.matcher(input).replaceAll(""); return STRIP_COLOR_PATTERN.matcher(input).replaceAll("");
} }
/**
* Translates a string using an alternate color code character into a string
* that uses the internal ChatColor.COLOR_CODE color code character. The
* alternate color code character will only be replaced if it is immediately
* followed by 0-9, A-F, or a-f.
*
* @param altColorChar The alternate color code character to replace. Ex: &
* @param textToTranslate Text containing the alternate color code
* character.
* @return Text containing the ChatColor.COLOR_CODE color code character.
*/
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) {
char[] b = textToTranslate.toCharArray();
for (int i = 0; i < b.length - 1; i++) {
if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) {
b[i] = ChatColor.COLOR_CHAR;
b[i + 1] = Character.toLowerCase(b[i + 1]);
}
}
return new String(b);
}
/**
* Gets the ChatColors used at the end of the given input string.
*
* @param input Input string to retrieve the colors from.
* @return Any remaining ChatColors to pass onto the next line.
*/
public static String getLastColors(String input) {
String result = "";
int length = input.length();
// Search backwards from the end as it is faster
for (int index = length - 1; index > -1; index--) {
char section = input.charAt(index);
if (section == COLOR_CHAR && index < length - 1) {
char c = input.charAt(index + 1);
ChatColor color = getByChar(c);
if (color != null) {
result = color.toString() + result;
// Once we find a color or reset we can stop searching
if (color.isColor() || color.equals(RESET)) {
break;
}
}
}
}
return result;
}
static {
for (ChatColor color : values()) {
BY_ID.put(color.intCode, color);
BY_CHAR.put(color.code, color);
}
}
} }

View File

@ -17,6 +17,9 @@ import static net.md_5.bungee.Logger.$;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
/**
* Core configuration for the proxy.
*/
public class Configuration { public class Configuration {
/** /**
@ -97,6 +100,9 @@ public class Configuration {
*/ */
public int logNumLines = 1 << 14; public int logNumLines = 1 << 14;
/**
* Load the configuration and save default values.
*/
public void load() { public void load() {
try { try {
file.createNewFile(); file.createNewFile();
@ -172,11 +178,17 @@ public class Configuration {
} }
} }
public String getServerFor(String user, String requestedHost) { /**
String server; * Get which server a user should be connected to, taking into account their
if (forcedServers.containsKey(requestedHost)) { * name and virtual host.
server = servers.get(forcedServers.get(requestedHost)); *
} else { * @param user to get a server for
* @param requestedHost the host which they connected to
* @return the name of the server which they should be connected to.
*/
public String getServer(String user, String requestedHost) {
String server = forcedServers.get(requestedHost);
if (server == null) {
server = reconnectLocations.get(user); server = reconnectLocations.get(user);
} }
if (server == null) { if (server == null) {
@ -185,20 +197,30 @@ public class Configuration {
return server; return server;
} }
public void setHostFor(UserConnection user, String server) { /**
* Save the last server which the user was on.
*
* @param user the name of the user
* @param server which they were last on
*/
public void setServer(UserConnection user, String server) {
reconnectLocations.put(user.username, server); reconnectLocations.put(user.username, server);
} }
/**
* Gets the connectable address of a server defined in the configuration.
*
* @param name the friendly name of a server
* @return the usable {@link InetSocketAddress} mapped to this server
*/
public InetSocketAddress getServer(String name) { public InetSocketAddress getServer(String name) {
String hostline = (name == null) ? defaultServerName : name; String server = servers.get((name == null) ? defaultServerName : name);
String server = servers.get(hostline); return (server != null) ? Util.getAddr(server) : getServer(null);
if (server != null) {
return Util.getAddr(server);
} else {
return getServer(null);
}
} }
/**
* Save the current mappings of users to servers.
*/
public void saveHosts() { public void saveHosts() {
save(reconnect, reconnectLocations); save(reconnect, reconnectLocations);
$().info("Saved reconnect locations to " + reconnect); $().info("Saved reconnect locations to " + reconnect);

View File

@ -35,6 +35,9 @@ import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV; import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* Class containing all encryption related methods for the proxy.
*/
public class EncryptionUtil { public class EncryptionUtil {
private static final Random secure = new SecureRandom(); private static final Random secure = new SecureRandom();
@ -57,9 +60,7 @@ public class EncryptionUtil {
return new PacketFDEncryptionRequest(hash, pubKey, verify); return new PacketFDEncryptionRequest(hash, pubKey, verify);
} }
public static SecretKey getSecret(PacketFCEncryptionResponse resp, PacketFDEncryptionRequest request) throws BadPaddingException, IllegalBlockSizeException, public static SecretKey getSecret(PacketFCEncryptionResponse resp, PacketFDEncryptionRequest request) throws BadPaddingException, IllegalBlockSizeException, IllegalStateException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
IllegalStateException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
Cipher cipher = Cipher.getInstance("RSA"); Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, keys.getPrivate()); cipher.init(Cipher.DECRYPT_MODE, keys.getPrivate());
byte[] decrypted = cipher.doFinal(resp.verifyToken); byte[] decrypted = cipher.doFinal(resp.verifyToken);

View File

@ -1,5 +1,8 @@
package net.md_5.bungee; package net.md_5.bungee;
/**
* Class to rewrite integers within packets.
*/
public class EntityMap { public class EntityMap {
public final static int[][] entityIds = new int[256][]; public final static int[][] entityIds = new int[256][];

View File

@ -9,6 +9,9 @@ import static net.md_5.bungee.Logger.$;
import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketFFKick;
import net.md_5.bungee.packet.PacketInputStream; import net.md_5.bungee.packet.PacketInputStream;
/**
* Class to represent a Minecraft connection.
*/
@EqualsAndHashCode @EqualsAndHashCode
@RequiredArgsConstructor @RequiredArgsConstructor
public class GenericConnection { public class GenericConnection {
@ -18,6 +21,11 @@ public class GenericConnection {
protected final OutputStream out; protected final OutputStream out;
public String username; public String username;
/**
* Close the socket with the specified reason.
*
* @param reason to disconnect
*/
public void disconnect(String reason) { public void disconnect(String reason) {
if (socket.isClosed()) { if (socket.isClosed()) {
return; return;

View File

@ -9,7 +9,7 @@ import net.md_5.bungee.packet.PacketFCEncryptionResponse;
import net.md_5.bungee.packet.PacketFDEncryptionRequest; import net.md_5.bungee.packet.PacketFDEncryptionRequest;
import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketFFKick;
import net.md_5.bungee.packet.PacketInputStream; import net.md_5.bungee.packet.PacketInputStream;
import net.md_5.bungee.plugin.ConnectEvent; import net.md_5.bungee.plugin.HandshakeEvent;
import org.bouncycastle.crypto.io.CipherInputStream; import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.io.CipherOutputStream; import org.bouncycastle.crypto.io.CipherOutputStream;
@ -34,8 +34,8 @@ public class InitialHandler implements Runnable {
case 0x02: case 0x02:
Packet2Handshake handshake = new Packet2Handshake(packet); Packet2Handshake handshake = new Packet2Handshake(packet);
// fire connect event // fire connect event
ConnectEvent event = new ConnectEvent(handshake.username, socket.getInetAddress()); HandshakeEvent event = new HandshakeEvent(handshake.username, socket.getInetAddress());
BungeeCord.instance.pluginManager.onConnect(event); BungeeCord.instance.pluginManager.onHandshake(event);
if (event.isCancelled()) { if (event.isCancelled()) {
throw new KickException(event.getCancelReason()); throw new KickException(event.getCancelReason());
} }
@ -59,8 +59,7 @@ public class InitialHandler implements Runnable {
} }
UserConnection userCon = new UserConnection(socket, in, out, handshake); UserConnection userCon = new UserConnection(socket, in, out, handshake);
userCon.register(); userCon.connect(BungeeCord.instance.config.getServer(handshake.username, handshake.host));
userCon.connect(BungeeCord.instance.config.getServerFor(handshake.username, handshake.host));
break; break;
case 0xFE: case 0xFE:
throw new KickException(BungeeCord.instance.config.motd + ChatColor.COLOR_CHAR + BungeeCord.instance.connections.size() + ChatColor.COLOR_CHAR + BungeeCord.instance.config.maxPlayers); throw new KickException(BungeeCord.instance.config.motd + ChatColor.COLOR_CHAR + BungeeCord.instance.connections.size() + ChatColor.COLOR_CHAR + BungeeCord.instance.config.maxPlayers);

View File

@ -1,5 +1,9 @@
package net.md_5.bungee; package net.md_5.bungee;
/**
* Exception, which when thrown will disconnect the player from the proxy with
* the specified message.
*/
public class KickException extends RuntimeException { public class KickException extends RuntimeException {
public KickException(String message) { public KickException(String message) {

View File

@ -7,6 +7,9 @@ import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import static net.md_5.bungee.Logger.$; import static net.md_5.bungee.Logger.$;
/**
* Thread to listen and dispatch incoming connections to the proxy.
*/
public class ListenThread extends Thread { public class ListenThread extends Thread {
public final ServerSocket socket; public final ServerSocket socket;

View File

@ -9,6 +9,9 @@ import java.util.logging.Formatter;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
/**
* Logger to handle formatting and storage of the proxy's logger.
*/
public class Logger extends java.util.logging.Logger { public class Logger extends java.util.logging.Logger {
private static final Formatter formatter = new ConsoleLogFormatter(); private static final Formatter formatter = new ConsoleLogFormatter();
@ -20,7 +23,7 @@ public class Logger extends java.util.logging.Logger {
FileHandler handler = new FileHandler("proxy.log", BungeeCord.instance.config.logNumLines, 1, true); FileHandler handler = new FileHandler("proxy.log", BungeeCord.instance.config.logNumLines, 1, true);
handler.setFormatter(formatter); handler.setFormatter(formatter);
addHandler(handler); addHandler(handler);
} catch (IOException | SecurityException ex) { } catch (IOException ex) {
System.err.println("Could not register logger!"); System.err.println("Could not register logger!");
ex.printStackTrace(); ex.printStackTrace();
} }
@ -37,6 +40,11 @@ public class Logger extends java.util.logging.Logger {
} }
} }
/**
* Gets the current logger instance.
*
* @return the current logger instance
*/
public static Logger $() { public static Logger $() {
return instance; return instance;
} }

View File

@ -1,5 +1,9 @@
package net.md_5.bungee; package net.md_5.bungee;
/**
* Class to call the {@link Configuration#saveHosts() } method at 5 minute
* intervals.
*/
public class ReconnectSaveThread extends Thread { public class ReconnectSaveThread extends Thread {
public ReconnectSaveThread() { public ReconnectSaveThread() {

View File

@ -16,6 +16,9 @@ import net.md_5.bungee.packet.PacketInputStream;
import org.bouncycastle.crypto.io.CipherInputStream; import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.io.CipherOutputStream; import org.bouncycastle.crypto.io.CipherOutputStream;
/**
* Class representing a connection from the proxy to the server; ie upstream.
*/
public class ServerConnection extends GenericConnection { public class ServerConnection extends GenericConnection {
public final String name; public final String name;

View File

@ -33,14 +33,11 @@ public class UserConnection extends GenericConnection implements CommandSender {
super(socket, in, out); super(socket, in, out);
this.handshake = handshake; this.handshake = handshake;
username = handshake.username; username = handshake.username;
BungeeCord.instance.connections.put(username, this);
} }
public void connect(String server) { public void connect(String server) {
InetSocketAddress addr = BungeeCord.instance.config.getServer(server); InetSocketAddress addr = BungeeCord.instance.config.getServer(server);
if (addr.equals(curServer())) {
sendMessage(ChatColor.RED + "You are already on this server");
return;
}
connect(server, addr); connect(server, addr);
} }
@ -83,14 +80,6 @@ public class UserConnection extends GenericConnection implements CommandSender {
} }
} }
public void register() {
BungeeCord.instance.connections.put(username, this);
}
private InetSocketAddress curServer() {
return (server == null) ? null : new InetSocketAddress(server.socket.getInetAddress(), server.socket.getPort());
}
private void destory(String reason) { private void destory(String reason) {
if (BungeeCord.instance.isRunning) { if (BungeeCord.instance.isRunning) {
BungeeCord.instance.connections.remove(username); BungeeCord.instance.connections.remove(username);
@ -98,9 +87,7 @@ public class UserConnection extends GenericConnection implements CommandSender {
disconnect(reason); disconnect(reason);
if (server != null) { if (server != null) {
server.disconnect("Quitting"); server.disconnect("Quitting");
} BungeeCord.instance.config.setServer(this, server.name);
if (server != null) {
BungeeCord.instance.config.setHostFor(this, server.name);
} }
} }

View File

@ -2,13 +2,15 @@ package net.md_5.bungee;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
/**
* Series of utility classes to perform various operations.
*/
public class Util { public class Util {
private static final int DEFAULT_PORT = 25565; private static final int DEFAULT_PORT = 25565;
/** /**
* Basic method to transform human readable addresses into usable address * Method to transform human readable addresses into usable address objects.
* objects.
* *
* @param hostline in the format of 'host:port' * @param hostline in the format of 'host:port'
* @return the constructed hostname + port. * @return the constructed hostname + port.
@ -23,17 +25,11 @@ public class Util {
} }
/** /**
* Turns a InetSocketAddress into a string that can be reconstructed later. * Gets the value of the first unsigned byte of the specified array. Useful
* for getting the id of a packet array .
* *
* @param address the address to serialize * @param b the array to read from
* @return * @return the unsigned value of the first byte
*/
public static String getAddr(InetSocketAddress address) {
return address.getAddress().getHostAddress() + ((address.getPort() != DEFAULT_PORT) ? ":" + address.getPort() : "");
}
/**
* Get the packet id of specified byte array.
*/ */
public static int getId(byte[] b) { public static int getId(byte[] b) {
return b[0] & 0xFF; return b[0] & 0xFF;
@ -57,10 +53,23 @@ public class Util {
return result.toString(); return result.toString();
} }
/**
* Formats an integer as a hex value.
*
* @param i the integer to format
* @return the hex representation of the integer
*/
public static String hex(int i) { public static String hex(int i) {
return String.format("0x%02X", i); return String.format("0x%02X", i);
} }
/**
* Constructs a pretty one line version of a {@link Throwable}. Useful for
* debugging.
*
* @param t the {@link Throwable} to format.
* @return a string representing information about the {@link Throwable}
*/
public static String exception(Throwable t) { public static String exception(Throwable t) {
return t.getClass().getSimpleName() + " : " + t.getMessage() + " @ " + t.getStackTrace()[0].getFileName() + ":" + t.getStackTrace()[0].getLineNumber(); return t.getClass().getSimpleName() + " : " + t.getMessage() + " @ " + t.getStackTrace()[0].getFileName() + ":" + t.getStackTrace()[0].getLineNumber();
} }

View File

@ -1,7 +1,9 @@
package net.md_5.bungee.command; package net.md_5.bungee.command;
import net.md_5.bungee.ChatColor; /**
* Class which represents a proxy command. The {@link #execute(net.md_5.bungee.command.CommandSender, java.lang.String[])
* } method will be called to dispatch the command.
*/
public abstract class Command { public abstract class Command {
/** /**
@ -12,20 +14,4 @@ public abstract class Command {
* the original command. * the original command.
*/ */
public abstract void execute(CommandSender sender, String[] args); public abstract void execute(CommandSender sender, String[] args);
/**
* Check if the arg count is at least the specified amount
*
* @param sender sender to send message to if unsuccessful
* @param args to check
* @param count to compare
* @return if the arguments are valid
*/
public final boolean testArgs(CommandSender sender, String[] args, int count) {
boolean valid = args.length >= count;
if (!valid) {
sender.sendMessage(ChatColor.RED + "Please review your argument count");
}
return valid;
}
} }

View File

@ -3,6 +3,9 @@ package net.md_5.bungee.command;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.ChatColor; import net.md_5.bungee.ChatColor;
/**
* Command to terminate the proxy instance. May only be used by the console.
*/
public class CommandEnd extends Command { public class CommandEnd extends Command {
@Override @Override

View File

@ -5,6 +5,9 @@ import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.ChatColor; import net.md_5.bungee.ChatColor;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
/**
* Command to list all players connected to the proxy.
*/
public class CommandList extends Command { public class CommandList extends Command {
@Override @Override
@ -14,7 +17,6 @@ public class CommandList extends Command {
for (UserConnection con : connections) { for (UserConnection con : connections) {
users.append(con.username); users.append(con.username);
users.append(ChatColor.RESET.toString());
users.append(", "); users.append(", ");
} }

View File

@ -5,6 +5,9 @@ import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.ChatColor; import net.md_5.bungee.ChatColor;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
/**
* Command to list and switch a player between availible servers.
*/
public class CommandServer extends Command { public class CommandServer extends Command {
@Override @Override

View File

@ -2,6 +2,9 @@ package net.md_5.bungee.command;
import net.md_5.bungee.ChatColor; import net.md_5.bungee.ChatColor;
/**
* Command sender representing the proxy console.
*/
public class ConsoleCommandSender implements CommandSender { public class ConsoleCommandSender implements CommandSender {
public static final ConsoleCommandSender instance = new ConsoleCommandSender(); public static final ConsoleCommandSender instance = new ConsoleCommandSender();

View File

@ -8,6 +8,11 @@ import java.io.DataOutput;
import lombok.Delegate; import lombok.Delegate;
import net.md_5.bungee.Util; import net.md_5.bungee.Util;
/**
* This class represents a packet which has been given a special definition. All
* subclasses can read and write to the backing byte array which can be
* retrieved via the {@link #getPacket()} method.
*/
public abstract class DefinedPacket implements DataInput, DataOutput { public abstract class DefinedPacket implements DataInput, DataOutput {
private interface Overriden { private interface Overriden {
@ -44,6 +49,12 @@ public abstract class DefinedPacket implements DataInput, DataOutput {
writeByte(id); writeByte(id);
} }
/**
* Gets the bytes that make up this packet.
*
* @return the bytes which make up this packet, either the original byte
* array or the newly written one.
*/
public byte[] getPacket() { public byte[] getPacket() {
return packet == null ? out.toByteArray() : packet; return packet == null ? out.toByteArray() : packet;
} }

View File

@ -8,6 +8,10 @@ import java.io.InputStream;
import net.md_5.bungee.Util; import net.md_5.bungee.Util;
import net.minecraft.server.Packet; import net.minecraft.server.Packet;
/**
* A specialized input stream to parse packets using the Mojang packet
* definitions and then return them as a byte array.
*/
public class PacketInputStream { public class PacketInputStream {
private final DataInputStream dataInput; private final DataInputStream dataInput;
@ -18,6 +22,12 @@ public class PacketInputStream {
dataInput = new DataInputStream(tracker); dataInput = new DataInputStream(tracker);
} }
/**
* Read an entire packet from the stream and return it as a byte array.
*
* @return the read packet
* @throws IOException when the underlying input stream throws an exception
*/
public byte[] readPacket() throws IOException { public byte[] readPacket() throws IOException {
tracker.out.reset(); tracker.out.reset();
int id = tracker.read(); int id = tracker.read();
@ -33,6 +43,10 @@ public class PacketInputStream {
} }
/**
* Input stream which will wrap another stream and copy all bytes read to a
* {@link ByteArrayOutputStream}.
*/
private class TrackingInputStream extends InputStream { private class TrackingInputStream extends InputStream {
private final ByteArrayOutputStream out = new ByteArrayOutputStream(); private final ByteArrayOutputStream out = new ByteArrayOutputStream();

View File

@ -80,13 +80,25 @@ import net.minecraft.server.Packet7UseEntity;
import net.minecraft.server.Packet8UpdateHealth; import net.minecraft.server.Packet8UpdateHealth;
import net.minecraft.server.Packet9Respawn; import net.minecraft.server.Packet9Respawn;
/**
* Class containing instances of all Vanilla Minecraft packets.
*/
public class VanillaPackets { public class VanillaPackets {
/**
* Array of packet instances ordered by packet id.
*/
public static Packet[] packets = new Packet[256]; public static Packet[] packets = new Packet[256];
private static void map(int id, Class clazz) { /**
* Adds a new instance of a packet class to the array storage.
*
* @param id of the packet to add
* @param clazz class of the packet to add
*/
private static void map(int id, Class<? extends Packet> clazz) {
try { try {
packets[id] = (Packet) clazz.getDeclaredConstructor().newInstance(); packets[id] = clazz.getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException ex) { } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException ex) {
$().severe("Could not register packet id " + Util.hex(id)); $().severe("Could not register packet id " + Util.hex(id));
} }

View File

@ -1,8 +1,21 @@
package net.md_5.bungee.plugin; package net.md_5.bungee.plugin;
/**
* An event which may be canceled and this be prevented from happening.
*/
public interface Cancellable { public interface Cancellable {
public void setCancelled(boolean cancelled); /**
* Sets the canceled state of this event.
*
* @param canceled whether this event is canceled or not
*/
public void setCancelled(boolean canceled);
/**
* Gets the canceled state of this event.
*
* @return whether this event is canceled or not
*/
public boolean isCancelled(); public boolean isCancelled();
} }

View File

@ -1,13 +0,0 @@
package net.md_5.bungee.plugin;
import java.net.InetAddress;
import lombok.Data;
@Data
public class ConnectEvent implements Cancellable {
private boolean cancelled;
private String cancelReason;
private final String username;
private final InetAddress address;
}

View File

@ -0,0 +1,30 @@
package net.md_5.bungee.plugin;
import java.net.InetAddress;
import lombok.Data;
/**
* Event called once a remote connection has begun the login procedure. This
* event is ideal for IP banning, however must be used with care in other places
* such as logging at the username has not yet been verified with Mojang.
*/
@Data
public class HandshakeEvent implements Cancellable {
/**
* Canceled state.
*/
private boolean cancelled;
/**
* Message to use when kicking if this event is canceled.
*/
private String cancelReason;
/**
* Username which the player wishes to use.
*/
private final String username;
/**
* IP address of the remote connection.
*/
private final InetAddress address;
}

View File

@ -1,5 +1,8 @@
package net.md_5.bungee.plugin; package net.md_5.bungee.plugin;
/**
* Exception thrown when a plugin could not be loaded for any reason.
*/
public class InvalidPluginException extends RuntimeException { public class InvalidPluginException extends RuntimeException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -3,6 +3,9 @@ package net.md_5.bungee.plugin;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.command.Command; import net.md_5.bungee.command.Command;
/**
* Base class which all proxy plugins should extend.
*/
public abstract class JavaPlugin { public abstract class JavaPlugin {
/** /**
@ -26,13 +29,13 @@ public abstract class JavaPlugin {
* Called when a user connects with their name and address. To keep things * Called when a user connects with their name and address. To keep things
* simple this name has not been checked with minecraft.net. * simple this name has not been checked with minecraft.net.
*/ */
public void onConnect(ConnectEvent event) { public void onHandshake(HandshakeEvent event) {
} }
/** /**
* Register a command. * Register a command for use with the proxy.
*/ */
protected void registerCommand(String label, Command command) { protected final void registerCommand(String label, Command command) {
BungeeCord.instance.commandMap.put(label, command); BungeeCord.instance.commandMap.put(label, command);
} }
} }

View File

@ -12,11 +12,22 @@ import java.util.zip.ZipEntry;
import lombok.Getter; import lombok.Getter;
import static net.md_5.bungee.Logger.$; import static net.md_5.bungee.Logger.$;
/**
* Plugin manager to handle loading and saving other JavaPlugin's. This class is
* itself a plugin for ease of use.
*/
public class JavaPluginManager extends JavaPlugin { public class JavaPluginManager extends JavaPlugin {
/**
* Set of loaded plugins.
*/
@Getter @Getter
private final Set<JavaPlugin> plugins = new HashSet<>(); private final Set<JavaPlugin> plugins = new HashSet<>();
/**
* Load all plugins from the plugins folder. This method must only be called
* once per instance.
*/
public void loadPlugins() { public void loadPlugins() {
File dir = new File("plugins"); File dir = new File("plugins");
dir.mkdir(); dir.mkdir();
@ -58,9 +69,9 @@ public class JavaPluginManager extends JavaPlugin {
} }
@Override @Override
public void onConnect(ConnectEvent event) { public void onHandshake(HandshakeEvent event) {
for (JavaPlugin p : plugins) { for (JavaPlugin p : plugins) {
p.onConnect(event); p.onHandshake(event);
} }
} }
} }

View File

@ -5,6 +5,10 @@ import java.lang.reflect.Field;
import lombok.Data; import lombok.Data;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
/**
* File which contains information about a plugin, its authors, and how to load
* it.
*/
@Data @Data
public class PluginDescription { public class PluginDescription {